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