ferencd@0: \chapter{Parsing and Building Messages} ferencd@0: ferencd@0: % ============================================================================ ferencd@0: \section{Parsing messages} ferencd@0: ferencd@0: \subsection{Introduction} % -------------------------------------------------- ferencd@0: ferencd@0: Parsing is the process of creating a structured representation (for example, ferencd@0: a hierarchy of C++ objects) of a message from its ``textual'' representation ferencd@0: (the raw data that is actually sent on the Internet). ferencd@0: ferencd@0: For example, say you have the following email in a file called "hello.eml": ferencd@0: ferencd@0: \begin{verbatim} ferencd@0: Date: Thu, Oct 13 2005 15:22:46 +0200 ferencd@0: From: Vincent ferencd@0: To: you@vmime.org ferencd@0: Subject: Hello from VMime! ferencd@0: ferencd@0: A simple message to test VMime ferencd@0: \end{verbatim} ferencd@0: ferencd@0: The following code snippet shows how you can easily obtain a ferencd@0: {\vcode vmime::message} object from data in this file: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Parsing a message from a file}] ferencd@0: // Read data from file ferencd@0: std::ifstream file; ferencd@0: file.open("hello.eml", std::ios::in | std::ios::binary); ferencd@0: ferencd@0: vmime::utility::inputStreamAdapter is(file); ferencd@0: ferencd@0: vmime::string data; ferencd@0: vmime::utility::outputStreamStringAdapter os(data); ferencd@0: ferencd@0: vmime::utility::bufferedStreamCopy(is, os); ferencd@0: ferencd@0: // Actually parse the message ferencd@0: vmime::shared_ptr msg = vmime::make_shared (); ferencd@0: msg->parse(data); ferencd@0: ferencd@0: vmime::shared_ptr hdr = msg->getHeader(); ferencd@0: vmime::shared_ptr bdy = msg->getBody(); ferencd@0: ferencd@0: // Now, you can extract some of its components ferencd@0: vmime::charset ch(vmime::charsets::UTF_8); ferencd@0: ferencd@0: std::cout ferencd@0: << "The subject of the message is: " ferencd@0: << hdr->Subject()->getValue ()->getConvertedText(ch) ferencd@0: << std::endl ferencd@0: << "It was sent by: " ferencd@0: << hdr->From()->getValue ()->getName().getConvertedText(ch) ferencd@0: << " (email: " << hdr->From()->getValue ()->getEmail() << ")" ferencd@0: << std::endl; ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: The output of this program is: ferencd@0: ferencd@0: \begin{verbatim} ferencd@0: The subject of the message is: Hello from VMime! ferencd@0: It was sent by: Vincent (email: vincent@vmime.org) ferencd@0: \end{verbatim} ferencd@0: ferencd@0: ferencd@0: \subsection{Using the {\vcode vmime::messageParser} object} % ---------------- ferencd@0: ferencd@0: The {\vcode vmime::messageParser} object allows to parse messages in a more ferencd@0: simple manner. You can obtain all the text parts and attachments as well as ferencd@0: basic fields (expeditor, recipients, subject...), without dealing with ferencd@0: MIME message structure. ferencd@0: ferencd@0: \begin{lstlisting}[caption={Using {\vcode vmime::messageParser} to parse ferencd@0: more complex messages}] ferencd@0: // Read data from file ferencd@0: std::ifstream file; ferencd@0: file.open("hello.eml", std::ios::in | std::ios::binary); ferencd@0: ferencd@0: vmime::utility::inputStreamAdapter is(file); ferencd@0: ferencd@0: vmime::string data; ferencd@0: vmime::utility::outputStreamStringAdapter os(data); ferencd@0: ferencd@0: vmime::utility::bufferedStreamCopy(is, os); ferencd@0: ferencd@0: // Actually parse the message ferencd@0: vmime::shared_ptr msg = vmime::make_shared (); ferencd@0: msg->parse(data); ferencd@0: ferencd@0: // Here start the differences with the previous example ferencd@0: vmime::messageParser mp(msg); ferencd@0: ferencd@0: // Output information about attachments ferencd@0: std::cout << "Message has " << mp.getAttachmentCount() ferencd@0: << " attachment(s)" << std::endl; ferencd@0: ferencd@0: for (int i = 0 ; i < mp.getAttachmentCount() ; ++i) ferencd@0: { ferencd@0: vmime::shared_ptr att = mp.getAttachmentAt(i); ferencd@0: std::cout << " - " << att->getType().generate() << std::endl; ferencd@0: } ferencd@0: ferencd@0: // Output information about text parts ferencd@0: std::cout << "Message has " << mp.getTextPartCount() ferencd@0: << " text part(s)" << std::endl; ferencd@0: ferencd@0: for (int i = 0 ; i < mp.getTextPartCount() ; ++i) ferencd@0: { ferencd@0: vmime::shared_ptr tp = mp.getTextPartAt(i); ferencd@0: ferencd@0: // text/html ferencd@0: if (tp->getType().getSubType() == vmime::mediaTypes::TEXT_HTML) ferencd@0: { ferencd@0: vmime::shared_ptr htp = ferencd@0: vmime::dynamicCast (tp); ferencd@0: ferencd@0: // HTML text is in tp->getText() ferencd@0: // Plain text is in tp->getPlainText() ferencd@0: ferencd@0: // Enumerate embedded objects ferencd@0: for (int j = 0 ; j < htp->getObjectCount() ; ++j) ferencd@0: { ferencd@0: vmime::shared_ptr obj = ferencd@0: htp->getObjectAt(j); ferencd@0: ferencd@0: // Identifier (Content-Id or Content-Location) is obj->getId() ferencd@0: // Object data is in obj->getData() ferencd@0: } ferencd@0: } ferencd@0: // text/plain or anything else ferencd@0: else ferencd@0: { ferencd@0: // Text is in tp->getText() ferencd@0: } ferencd@0: } ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: ferencd@0: % ============================================================================ ferencd@0: \section{Building messages} ferencd@0: ferencd@0: \subsection{A simple message\label{msg-building-simple-message}} % ----------- ferencd@0: ferencd@0: Of course, you can build a MIME message from scratch by creating the various ferencd@0: objects that compose it (parts, fields, etc.). The following is an example of ferencd@0: how to achieve it: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Building a simple message from scratch}] ferencd@0: vmime::shared_ptr msg = vmime::make_shared (); ferencd@0: ferencd@0: vmime::shared_ptr hdr = msg->getHeader(); ferencd@0: vmime::shared_ptr bdy = msg->getBody(); ferencd@0: ferencd@0: vmime::shared_ptr hfFactory = ferencd@0: vmime::headerFieldFactory::getInstance(); ferencd@0: ferencd@0: // Append a 'Date:' field ferencd@0: vmime::shared_ptr dateField = ferencd@0: hfFactory->create(vmime::fields::DATE); ferencd@0: ferencd@0: dateField->setValue(vmime::datetime::now()); ferencd@0: hdr->appendField(dateField); ferencd@0: ferencd@0: // Append a 'Subject:' field ferencd@0: vmime::shared_ptr subjectField = ferencd@0: hfFactory->create(vmime::fields::SUBJECT); ferencd@0: ferencd@0: subjectField->setValue(vmime::text("Message subject")); ferencd@0: hdr->appendField(subjectField); ferencd@0: ferencd@0: // Append a 'From:' field ferencd@0: vmime::shared_ptr fromField = ferencd@0: hfFactory->create(vmime::fields::FROM); ferencd@0: ferencd@0: fromField->setValue ferencd@0: (vmime::make_shared ("me@vmime.org")); ferencd@0: hdr->appendField(fromField); ferencd@0: ferencd@0: // Append a 'To:' field ferencd@0: vmime::shared_ptr toField = ferencd@0: hfFactory->create(vmime::fields::TO); ferencd@0: ferencd@0: vmime::shared_ptr recipients = ferencd@0: vmime::make_shared (); ferencd@0: ferencd@0: recipients->appendMailbox ferencd@0: (vmime::make_shared ("you@vmime.org")); ferencd@0: ferencd@0: toField->setValue(recipients); ferencd@0: hdr->appendField(toField); ferencd@0: ferencd@0: // Set the body contents ferencd@0: bdy->setContents(vmime::make_shared ferencd@0: ("This is the text of your message...")); ferencd@0: ferencd@0: // Output raw message data to standard output ferencd@0: vmime::utility::outputStreamAdapter out(std::cout); ferencd@0: msg->generate(out); ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: As you can see, this is a little fastidious. Hopefully, VMime also offers a ferencd@0: more simple way for creating messages. The {\vcode vmime::messageBuilder} ferencd@0: object can create basic messages that you can then customize. ferencd@0: ferencd@0: The following code can be used to build exactly the same message as in the ferencd@0: previous example, using the {\vcode vmime::messageBuilder} object: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Building a simple message ferencd@0: using {\vcode vmime::messageBuilder}}] ferencd@0: try ferencd@0: { ferencd@0: vmime::messageBuilder mb; ferencd@0: ferencd@0: // Fill in some header fields and message body ferencd@0: mb.setSubject(vmime::text("Message subject")); ferencd@0: mb.setExpeditor(vmime::mailbox("me@vmime.org")); ferencd@0: mb.getRecipients().appendAddress ferencd@0: (vmime::make_shared ("you@vmime.org")); ferencd@0: ferencd@0: mb.getTextPart()->setCharset(vmime::charsets::ISO8859_15); ferencd@0: mb.getTextPart()->setText(vmime::make_shared ferencd@0: ("This is the text of your message...")); ferencd@0: ferencd@0: // Message construction ferencd@0: vmime::shared_ptr msg = mb.construct(); ferencd@0: ferencd@0: // Output raw message data to standard output ferencd@0: vmime::utility::outputStreamAdapter out(std::cout); ferencd@0: msg->generate(out); ferencd@0: } ferencd@0: // VMime exception ferencd@0: catch (vmime::exception& e) ferencd@0: { ferencd@0: std::cerr << "vmime::exception: " << e.what() << std::endl; ferencd@0: } ferencd@0: // Standard exception ferencd@0: catch (std::exception& e) ferencd@0: { ferencd@0: std::cerr << "std::exception: " << e.what() << std::endl; ferencd@0: } ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: ferencd@0: \subsection{Adding an attachment} % ------------------------------------------ ferencd@0: ferencd@0: Dealing with attachments is quite simple. Add the following code to the ferencd@0: previous example to attach a file to the message: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Building a message with an attachment using ferencd@0: {\vcode vmime::messageBuilder}}] ferencd@0: // Create an attachment ferencd@0: vmime::shared_ptr att = ferencd@0: vmime::make_shared ferencd@0: ( ferencd@0: /* full path to file */ "/home/vincent/paris.jpg", ferencd@0: /* content type */ vmime::mediaType("image/jpeg), ferencd@0: /* description */ vmime::text("My holidays in Paris") ferencd@0: ); ferencd@0: ferencd@0: // You can also set some infos about the file ferencd@0: att->getFileInfo().setFilename("paris.jpg"); ferencd@0: att->getFileInfo().setCreationDate ferencd@0: (vmime::datetime("30 Apr 2003 14:30:00 +0200")); ferencd@0: ferencd@0: // Add this attachment to the message ferencd@0: mb.appendAttachment(att); ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: ferencd@0: \subsection{HTML messages and embedded objects} % ---------------------------- ferencd@0: ferencd@0: VMime also supports aggregate messages, which permits to build MIME messages ferencd@0: containing HTML text and embedded objects (such as images). For more information ferencd@0: about aggregate messages, please read RFC-2557 (\emph{MIME Encapsulation of ferencd@0: Aggregate Documents, such as HTML}). ferencd@0: ferencd@0: Creating such messages is quite easy, using the {\vcode vmime::messageBuilder} ferencd@0: object. The following code constructs a message containing text in both plain ferencd@0: and HTML format, and a JPEG image: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Building an HTML message with an embedded image ferencd@0: using the {\vcode vmime::messageBuilder}}] ferencd@0: // Fill in some header fields ferencd@0: mb.setSubject(vmime::text("An HTML message")); ferencd@0: mb.setExpeditor(vmime::mailbox("me@vmime.org")); ferencd@0: mb.getRecipients().appendAddress ferencd@0: (vmime::make_shared ("you@vmime.org")); ferencd@0: ferencd@0: // Set the content-type to "text/html": a text part factory must be ferencd@0: // available for the type you are using. The following code will make ferencd@0: // the message builder construct the two text parts. ferencd@0: mb.constructTextPart(vmime::mediaType ferencd@0: (vmime::mediaTypes::TEXT, vmime::mediaTypes::TEXT_HTML)); ferencd@0: ferencd@0: // Set contents of the text parts; the message is available in two formats: ferencd@0: // HTML and plain text. The HTML format also includes an embedded image. ferencd@0: vmime::shared_ptr textPart = ferencd@0: vmime::dynamicCast (mb.getTextPart()); ferencd@0: ferencd@0: // -- Add the JPEG image (the returned identifier is used to identify the ferencd@0: // -- embedded object in the HTML text, the famous "CID", or "Content-Id"). ferencd@0: // -- Note: you can also read data from a file; see the next example. ferencd@0: const vmime::string id = textPart->addObject("<...image data...>", ferencd@0: vmime::mediaType(vmime::mediaTypes::IMAGE, vmime::mediaTypes::IMAGE_JPEG)); ferencd@0: ferencd@0: // -- Set the text ferencd@0: textPart->setCharset(vmime::charsets::ISO8859_15); ferencd@0: ferencd@0: textPart->setText(vmime::make_shared ferencd@0: ("This is the HTML text, and the image:
" ferencd@0: "")); ferencd@0: ferencd@0: textPart->setPlainText(vmime::make_shared ferencd@0: ("This is the plain text.")); ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: This will create a message having the following structure: ferencd@0: ferencd@0: \begin{verbatim} ferencd@0: multipart/alternative ferencd@0: text/plain ferencd@0: multipart/related ferencd@0: text/html ferencd@0: image/jpeg ferencd@0: \end{verbatim} ferencd@0: ferencd@0: You can easily tell VMime to read the embedded object data from a file. The ferencd@0: following code opens the file \emph{/path/to/image.jpg}, connects it to an ferencd@0: input stream, then add an embedded object: ferencd@0: ferencd@0: \begin{lstlisting} ferencd@0: vmime::utility::fileSystemFactory* fs = ferencd@0: vmime::platform::getHandler()->getFileSystemFactory(); ferencd@0: ferencd@0: vmime::shared_ptr imageFile = ferencd@0: fs->create(fs->stringToPath("/path/to/image.jpg")); ferencd@0: ferencd@0: vmime::shared_ptr imageCts = ferencd@0: vmime::make_shared ferencd@0: (imageFile->getFileReader()->getInputStream(), imageFile->getLength()); ferencd@0: ferencd@0: const vmime::string cid = textPart.addObject(imageCts, ferencd@0: vmime::mediaType(vmime::mediaTypes::IMAGE, vmime::mediaTypes::IMAGE_JPEG)); ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: ferencd@0: % ============================================================================ ferencd@0: \section{Working with attachments: the attachment helper} ferencd@0: ferencd@0: The {\vcode attachmentHelper} object allows listing all attachments in a ferencd@0: message, as well as adding new attachments, without using the ferencd@0: {\vcode messageParser} and {\vcode messageBuilders} objects. It can work ferencd@0: directly on messages and body parts. ferencd@0: ferencd@0: To use it, you do not need any knowledge about how attachment parts should ferencd@0: be organized in a MIME message. ferencd@0: ferencd@0: The following code snippet tests if a body part is an attachment, and if so, ferencd@0: extract its contents to the standard output: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Testing if a body part is an attachment}] ferencd@0: vmime::shared_ptr part; // suppose we have a body part ferencd@0: ferencd@0: if (vmime::attachmentHelper::isBodyPartAnAttachment(part)) ferencd@0: { ferencd@0: // The body part contains an attachment, get it ferencd@0: vmime::shared_ptr attach = ferencd@0: attachmentHelper::getBodyPartAttachment(part); ferencd@0: ferencd@0: // Extract attachment data to standard output ferencd@0: vmime::utility::outputStreamAdapter out(std::cout); ferencd@0: attach->getData()->extract(out); ferencd@0: } ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: You can also easily extract all attachments from a message: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Extracting all attachments from a message}] ferencd@0: vmime::shared_ptr msg; // suppose we have a message ferencd@0: ferencd@0: const std::vector > atts = ferencd@0: attachmentHelper::findAttachmentsInMessage(msg); ferencd@0: \end{lstlisting} ferencd@0: ferencd@0: Finally, the {\vcode attachmentHelper} object can be used to add an ferencd@0: attachment to an existing message, whatever it contains (text parts, ferencd@0: attachments, ...). The algorithm can modify the structure of the ferencd@0: message if needed (eg. add a \emph{multipart/mixed} part if no one ferencd@0: exists in the message). Simply call the {\vcode addAttachment} ferencd@0: function: ferencd@0: ferencd@0: \begin{lstlisting}[caption={Adding an attachment to an existing message}] ferencd@0: vmime::shared_ptr msg; // suppose we have a message ferencd@0: ferencd@0: // Create an attachment ferencd@0: vmime::shared_ptr att = ferencd@0: vmime::make_shared ferencd@0: ( ferencd@0: /* full path to file */ "/home/vincent/paris.jpg", ferencd@0: /* content type */ vmime::mediaType("image/jpeg), ferencd@0: /* description */ vmime::text("My holidays in Paris") ferencd@0: ); ferencd@0: ferencd@0: // Attach it to the message ferencd@0: vmime::attachmentHelper::addAttachment(msg, att); ferencd@0: \end{lstlisting} ferencd@0: