1
2
3                   T U T O R I A L    F O R   M I M E + +
4
51. Introduction
6
7Welcome to MIME++, a C++ class library for creating, parsing, and modifying
8messages in MIME format.  MIME++ has been designed specifically with the
9following objectives in mind:
10
11  * Create classes that directly correspond to the elements described in
12    RFC-822, RFC-2045, and other MIME-related documents.
13
14  * Create a library that is easy to use.
15
16  * Create a library that is extensible.
17
18MIME++ classes directly model the elements of the BNF grammar specified in
19RFC-822, RFC-2045, and RFC-2046.  For this reason, I recommend that you
20understand these RFCs and keep a copy of them handy as you learn MIME++.
21If you know C++ well, and if you are familiar with the RFCs, you should find
22MIME++ easy to learn and use.  If you are new to C++ and object-oriented
23programming, you will find in MIME++ some very good object-oriented
24techinques, and hopefully you will learn a lot.
25
26Before looking at the MIME++ classes, it is important to understand how
27MIME++ represents a message.  There are two representations of a message.
28The first is a string representation, in which a message is considered
29simply a sequence of characters.  The second is a 'broken-down' -- that is,
30parsed -- representation, in which the message is represented as a tree of
31components.
32
33The tree will be explained later, but for now, let's consider the
34relationship between the string representation and the broken-down
35representation.  When you create a new message, the string representation
36is initially empty.  After you set the contents of the broken-down
37representation, such as the header fields and the message body, you then
38assemble the message from its broken-down representation into its string
39representation.  The assembling is done through a call to the Assemble()
40member function of the DwMessage class.  Conversely, when you receive a
41message, it is received in its string representation, and you parse the
42message to create its broken-down representation.  The parsing is done
43through a call to the Parse() member function of the DwMessage class.
44From the broken-down representation, you can access the header fields, the
45body, and so on. If you want to modify a received message, you can change
46the contents of the broken-down representation, then assemble the message
47to create the modified string representation.  Because of the way MIME++
48implements the broken-down representation, only those specific components
49that were modified in the broken-down representation will be modified in
50the new string representation.
51
52The broken-down representation takes the form of a tree.  The idea for the
53tree comes from the idea that a message can be broken down into various
54components, and that the components form a hierarchy.  At the highest
55level, we have the complete message.  We can break the message down into a
56header and a body to arrive at the second-highest level.  We can break the
57header down into a collection of header fields.  We can break each header
58field down into a field-name and a field-body.  If the header field is a
59structured field, we can further break down its field-body into components
60specific to that field-body, such as a local-part and domain for a
61mailbox.  Now, we can think of each component of the message as a node in
62the tree.  The top, or root, node is the message itself.  Below that, the
63message node contains child nodes for the header and body; the header node
64contains a child node for each header field; and so on.  Each node
65contains a substring of the entire message, and a node's string is the
66concatenation of all of its child nodes' strings.
67
68In the MIME++ implementation, the abstract base class DwMessageComponent
69encapsulates all the common attributes and behavior of the tree's nodes.
70The most important member functions of DwMessageComponent are Parse() and
71Assemble(), which are declared as pure virtual functions.  Normally, you
72would use these member functions only as operations on objects of the
73class DwMessage, a subclass of DwMessageComponent.  Parse() builds the
74entire tree of components with the DwMessage object at the root.
75Assemble() builds the string representation of the DwMessage object by
76traversing the tree and concatenating the strings of the leaf nodes.
77While every node in the tree is a DwMessageComponent, and therefore has a
78Parse() and Assemble() member function, you do not have to call these
79member functions for every node in the tree.  The reason is that both of
80these functions traverse the subtree rooted at the current node.  Parse()
81acts first on the current node, then calls the Parse() member function of
82its child nodes.  Assemble() first calls the Assemble() member functions
83of a node's child nodes, then concatenates the string representations of
84its child nodes.  Therefore, when you call Parse() or Assemble() for an
85object of the class DwMessage, Parse() or Assemble() will be called
86automatically for every component (that is, child node) in the message.
87
88DwMessageComponent also has one important attribute that you should be aware
89of.  That attribute is an is-modified flag (aka dirty flag), which is
90cleared whenever Parse() or Assemble() is called, and is set whenever the
91broken-down representation is modified.  To understand how this works,
92suppose you have just called Parse() on a DwMessage object to create its
93broken-down representation.  If you add a new DwField object (representing a
94new header field) to the DwHeaders object (representing the header), the
95is-modified flag will be set for the DwHeaders object, indicating that the
96string representation of the DwHeaders object will have to be re-assembled
97from the header fields that it contains.  When a node's is-modified flag is
98set, it also notifies its parent node to set its is-modified flag.  Thus,
99when the DwHeaders object's is-modified flag is set, the DwMessage object
100that is its parent will also have its is-modified flag set.  That way, when
101Assemble() is called for the DwMessage object, it will call the Assemble()
102member function for the DwHeaders object, as required.  Notice that the value
103of having an is-modified flag is that it can purge the tree traversal when
104the string representation of a message is being assembled.
105
106One of the first classes you should become familiar with is the DwString
107class, which handles character strings in MIME++.  DwString has been
108designed to handle very large character strings, so it may be different
109from string classes in other libraries.  Most of the standard C library
110string fuctions have DwString counterparts in MIME++.  These functions
111all start with "Dw", and include DwStrcpy(), DwStrcmp(), DwStrcasecmp(),
112and so on.  In addition, the equality operators and assignment operators
113work as expected.  If you have used string classes from other libraries,
114you will find DwString fairly intuitive.
115
116The following sections describe how to create, parse, and modify a
117message.  You should also look at the example programs included with the
118distribution.  These example programs are well-commented and use wrapper
119classes.  The wrapper classes BasicMessage, MultipartMessage, and
120MessageWithAttachments, are designed with three purposes in mind.  First,
121if your requirements are very modest -- say you just want to send a few
122files as attachments -- then you may find these classes to be adequate for
123your needs, and you will not have to learn the MIME++ library classes.
124Second, wrapper classes are the recommended way to use MIME++.  You should
125consider starting with these classes and customizing them for your own
126application.  Using wrapper classes will simplify the use of the MIME++
127library, but will also help to shield your application from future changes
128in the MIME++ library.  Third, these classes provide excellent examples for
129how to use the MIME++ library classes.
130
131The rest of this tutorial focuses on the library classes themselves.
132
133
1342. Creating a Message
135
136Creating a message with MIME++ involves instantiating a DwMessage object,
137setting values for its parts, and assembling the message into its final
138string representation.  The following simple example shows how to
139accomplish this.
140
141
142      void SendMessage(
143          const char* aTo,
144          const char* aFrom,
145          const char* aSubject,
146          const char* aBody)
147      {
148          // Create an empty message
149
150          DwMessage msg;
151
152          // Set the header fields.
153          // [ Note that a temporary DwString object is created for
154          // the argument for FromString() using the
155          // DwString::DwString(const char*) constructor. ]
156
157          DwHeaders& headers = msg.Headers();
158          headers.MessageId().CreateDefault();
159          headers.Date().FromCalendarTime(time(NULL)); //current date, time
160          headers.To().FromString(aTo);
161          headers.From().FromString(aFrom);
162          headers.Subject().FromString(aSubject);
163
164          // Set the message body
165
166          msg.Body().FromString(aBody);
167
168          // Assemble the message from its parts
169
170          msg.Assemble();
171
172          // Finally, send it.  In this example, just print it to the
173          // cout stream.
174
175          cout << msg.AsString();
176      }
177
178
179In this example, we set the fields 'Message-Id', 'Date', 'To', 'From', and
180'Subject', which are all documented in RFC-822.  The MIME++ class DwHeaders
181directly supports all header fields documented in RFC-822, RFC-2045, and
182RFC-1036.  To access the field-body for any one these fields, use the
183member function from DwHeaders that has a name corresponding to the
184field-name for that field.  The correspondence between a field-name and
185the name of the member function in DwHeaders is consistent: hyphens are
186dropped and the first character after the hyphen is capitalized.  Thus,
187field-name Content-type in RFC-1521 corresponds to the member function
188name ContentType.  These field-body access functions create an empty field
189in the headers if that field does not already exist.  To check if a
190particular field exists already, DwHeaders provides member functions
191HasXxxxx(); for example, HasSender(), HasMimeVersion(), or HasXref()
192will indicate whether the DwHeaders object has a 'Sender' field, a
193'MIME-Version' field, or an 'Xref' field, respectively.
194
195In the example, we used the FromString() member function of
196DwMessageComponent to set the string representation of the field-bodies.
197This is the simplest way to set the contents of a DwFieldBody object.
198Many of the field-bodies also have a broken-down represenation, and it is
199possible to set the parts of the broken-down representation.  Consider, for
200example, the DwDateTime class, which represents the date-time element of the
201BNF grammar specified in RFC-822.  In the example above, we did not set the
202string representation -- that would be more difficult and error prone.
203Instead we set the contents from the time_t value returned from a call to
204the ANSI C function time().  The DwDateTime class also contains member
205functions for setting individual attributes.  For example, we could have
206used the following code:
207
208      DwDateTime& date = msg.Headers().Date();
209      time_t t = time(NULL);
210      struct tm stm = *localtime(&t);
211      date.SetYear(stm.tm_year);
212      date.SetMonth(stm.tm_mon);
213      date.SetDay(stm.tm_mday);
214      date.SetHour(stm.tm_hour);
215      date.SetMinute(stm.tm_min);
216
217
2183. Parsing a Message
219
220Parsing a received message with MIME++ involves instantiating a DwMessage
221object, setting its string representation to contain the message, and then
222calling the Parse() member function of the DwMessage object.  The
223following simple example shows how to accomplish this.
224
225      void ParseMessage(DwString& aMessageStr)
226      {
227          // Create a message object
228          // We can set the message's string representation directly from the
229          // constructor, as in the uncommented version.  Or, we can use the
230          // default constructor and set its string representation using
231          // the member function DwMessage::FromString(), as in the
232          // commented version.
233
234          DwMessage msg(aMessageStr);
235
236          // Alternate technique:
237          // DwMessage msg;                 // Default constructor
238          // msg.FromString(aMessageStr);   // Set its string representation
239
240          // Execute the parse method, which will create the broken-down
241          // representation (the tree representation, if you recall)
242
243          msg.Parse();
244
245          // Print some of the header fields, just to show how it's done
246
247          // Date field.  First check if the field exists, since
248		  // DwHeaders::Date() will create it if is not found.
249
250          if (msg.Headers().HasDate()) {
251              cout << "Date of message is "
252                   << msg.Headers().Date().AsString()
253                   << '\n';
254          }
255
256          // From field.  Here we access the broken-down field body, too,
257		  // to get the full name (which may be empty), the local part,
258		  // and the domain of the first mailbox.  (The 'From' field can
259		  // have a list of mailboxes).
260
261          if (msg.Headers().HasFrom()) {
262              DwMailboxList& from = msg.Headers().From();
263              cout << "Message is from ";
264
265              // Get first mailbox, then iterate through the list
266
267              int isFirst = 1;
268              DwMailbox* mb = from.FirstMailbox();
269              while (mb) {
270                  if (isFirst) {
271                      isFirst = 0;
272                  }
273                  else {
274                      cout << ", ";
275                  }
276                  DwString& fullName = mb->FullName();
277                  if (fullName != "") {
278                      cout << fullName << '\n';
279                  }
280                  else {
281                      // Apparently, there is no full name, so use the email
282                      // address
283                      cout << mb->LocalPart() << '@' << mb->Domain() << '\n';
284                  }
285                  mb = mb->Next();
286              }
287
288          }
289
290          // Finally, print the message body, just to show how the body is
291          // retrieved.
292
293          cout << msg.Body().AsString() << '\n';
294      }
295
296Once you have parsed the message, you can access any of its parts.  The
297field-bodies of well-known header fields can be accessed by calling member
298functions of DwHeaders.  Some examples follow.
299
300    DwMediaType& contType = msg.Headers().ContentType();
301    DwMechanism& cte = msg.Headers().ContentTransferEncoding();
302    DwDateTime& date = msg.Headers().Date();
303
304The various subclasses of DwFieldBody, including DwMediaType, DwMechanism,
305and DwDateTime above, have member functions that allow you to access the parts
306of the field-body.  For example, DwMediaType has member functions to allow
307you to access its type, subtype, and parameters.  If the message is a
308multipart message, you may access the body parts by calling member
309functions of the class DwBody.  See the example code in multipar.cpp for
310an example of how to do this.
311
312
3134. Modifying a Message
314
315Modifying a message combines the procedures of parsing a message and
316creating a message.  First, parse the message, as explained above.  Then
317set the values of the components -- field-bodies, new fields, new body
318parts, or what have you -- that you wish to modify.  Finally, call the
319Assemble() member function of the DwMessage object to reassemble the
320message.  You can then access the modified message by calling
321DwMessage::AsString().  These final steps are the same as those involved
322in creating a new message.
323
324
3255. Customizing MIME++ Classes
326
327MIME++ has been designed to be easily customizable.  Typically, you
328customize C++ library classes through inheritance.  MIME++ allows you to
329create subclasses of most of its library classes in order to change their
330behavior.  MIME++ also includes certain 'hooks', which make it far easier
331to customize certain parts of the library.
332
333The most common customization is that of changing the way header fields
334are dealt with.  This could include adding the ability to handle certain
335non-standard header fields, or to change the way the field-bodies of
336certain standard header fields are interpreted or parsed.  As an example of
337the former customization, you may want to add the 'X-status' field or
338'X-sender' field to your messages.  As an example of the latter, you may
339want to change DwMediaType so that it will handle other MIME subtypes.
340
341Let's begin with the latter situation -- that of subclassing DwMediaType.
342Obviously, you will have to become familiar with DwMediaType and its
343superclasses before you change its behavior.  Then, at a minimum, you will
344want to provide your own implementation of the virtual member functions
345Parse() and Assemble().  Once you feel comfortable with the behavior of
346the behavior of your new class -- call it MyMediaType -- you will have to
347take the right steps to ensure that the MIME++ library internal routines
348will create objects of type MyMediaType, and not DwMediaType.  There are
349three such steps.
350
351First, define a function NewMyMediaType(), matching the prototype
352
353      DwMediaType* NewMyMediaType(
354	      const DwString& aStr,
355		  DwMessage* aParent)
356
357that creates a new instance of MyMediaType and returns it.  Set the static
358data member DwMediaType::sNewMediaType to point to this function.
359DwMediaType::sNewMediaType is normally NULL, meaning that no user-defined
360function is available.  When you set this static data member, however,
361MIME++'s internal routines will call your own function, and will therefore
362be able to create instances of your subclass.
363
364Second, make sure you have reimplemented the virtual function
365DwMediaType::Clone() to return a clone of your own subclassed object.
366Clone() serves as a 'virtual constructor'.  (See the discussion of virtual
367constructors in Stroustrup's _The C++ Programming Language_, 2nd Ed).
368
369Third, you should define a function CreateFieldBody(), matching the
370prototype
371
372      DwFieldBody* CreateFieldBody(
373          const DwString& aFieldName,
374          const DwString& aFieldBody,
375          DwMessageComponent* aParent)
376
377that returns an object of a subclass of DwFieldBody.  (DwFieldBody is a
378superclass of MyMediaType).  CreateFieldBody() is similar to the
379NewMyMediaType() function already described, except that its first
380argument supplies the field-name for the particular field currently being
381handled by MIME++.  CreateFieldBody() should examine the field-name,
382create an object of the appropriate subclass of DwFieldBody, and return a
383pointer to the object.  In this particular case, you need to make sure
384that when the field-name is 'Content-Type' you return an object of the
385class MyMediaType.  Set the hook for CreateFieldBody() setting the static
386data member DwField::sCreateFieldBody to point to your CreateFieldBody()
387function.  DwField::sCreateFieldBody is normally NULL when no user
388function is provided.
389
390These three steps are sufficient to ensure that your subclass of
391DwMediaType is integrated with the other MIME++ classes.
392
393The other customization task mentioned above is that of adding support for
394a non-standard header field.  There is a simple way to do this, and a way
395that involves creating a subclass of DwHeaders.  You can access any header
396field by calling DwHeaders's member functions.  In fact, you can iterate
397over all the header fields if you would like.  Therefore, the really
398simple way is just to not change anything and just use existing member
399functions.  The relevant functions include DwHeaders::HasField(), which will
400return a boolean value indicating if the header has the specified field,
401and DwHeaders::FieldBody(), which will return the DwFieldBody object
402associated with a specified field.  [ Note that DwHeaders::FieldBody() will
403create a field if it is not found. ] The default DwFieldBody subclass,
404which applies to all header fields not recognized by MIME++, is DwText,
405which is suitable for the unstructured field-bodies described in RFC-822
406such as 'Subject', 'Comments', and so on.  If a DwText object is suitable
407for your non-standard header field, then you don't have to do anything at all.
408Suppose, however, that you want an object of your own subclass of
409DwFieldBody, say StatusFieldBody, to be attached to the 'X-status' field.
410In this case, you will need to set the hook DwField::sCreateFieldBody as
411discussed above.  Your CreateFieldBody() function should return an
412instance of StatusFieldBody whenever the field-name is 'X-status'.
413
414Finally, while you can access any header field using DwHeaders's member
415functions, you may want to create your own subclass of DwHeaders for some
416reason or other -- maybe to add a convenience function to access the
417'X-status' header field.  To ensure that your new class is integrated with
418the library routines, you basically follow steps 1 and 2 above for
419subclassing DwFieldBody.  First, define a function NewMyHeaders() and set the
420static data member DwHeaders::sNewHeaders to point to your function. Second,
421make sure you have reimplemented the virtual function DwHeaders::Clone() to
422return an instance of your subclass.  Step 3 for subclassing DwFieldBody
423does not apply when subclassing DwHeaders.
424
425
4266. Getting Help
427
428I will try to help anyone who needs help specific to MIME++.  I won't try
429to answer general questions about C++ that could be answered by any C++
430expert.  Bug reports will receive the highest priority.  Other questions
431about how to do something I will try to answer in time, but I ask for your
432patience.  If you have any comments -- perhaps maybe you know of a better
433way to do something -- please send them.  My preferred email is
434dwsauder@fwb.gulf.net, but dwsauder@tasc.com is also acceptable.
435
436Good luck!
437
438$Revision: 1.3 $
439$Date: 1997/09/27 11:53:27 $
440