annotate 3rdparty/vmime/doc/book/msg.tex @ 0:a4671277546c tip

created the repository for the thymian project
author ferencd
date Tue, 17 Aug 2021 11:19:54 +0200
parents
children
rev   line source
ferencd@0 1 \chapter{Parsing and Building Messages}
ferencd@0 2
ferencd@0 3 % ============================================================================
ferencd@0 4 \section{Parsing messages}
ferencd@0 5
ferencd@0 6 \subsection{Introduction} % --------------------------------------------------
ferencd@0 7
ferencd@0 8 Parsing is the process of creating a structured representation (for example,
ferencd@0 9 a hierarchy of C++ objects) of a message from its ``textual'' representation
ferencd@0 10 (the raw data that is actually sent on the Internet).
ferencd@0 11
ferencd@0 12 For example, say you have the following email in a file called "hello.eml":
ferencd@0 13
ferencd@0 14 \begin{verbatim}
ferencd@0 15 Date: Thu, Oct 13 2005 15:22:46 +0200
ferencd@0 16 From: Vincent <vincent@vmime.org>
ferencd@0 17 To: you@vmime.org
ferencd@0 18 Subject: Hello from VMime!
ferencd@0 19
ferencd@0 20 A simple message to test VMime
ferencd@0 21 \end{verbatim}
ferencd@0 22
ferencd@0 23 The following code snippet shows how you can easily obtain a
ferencd@0 24 {\vcode vmime::message} object from data in this file:
ferencd@0 25
ferencd@0 26 \begin{lstlisting}[caption={Parsing a message from a file}]
ferencd@0 27 // Read data from file
ferencd@0 28 std::ifstream file;
ferencd@0 29 file.open("hello.eml", std::ios::in | std::ios::binary);
ferencd@0 30
ferencd@0 31 vmime::utility::inputStreamAdapter is(file);
ferencd@0 32
ferencd@0 33 vmime::string data;
ferencd@0 34 vmime::utility::outputStreamStringAdapter os(data);
ferencd@0 35
ferencd@0 36 vmime::utility::bufferedStreamCopy(is, os);
ferencd@0 37
ferencd@0 38 // Actually parse the message
ferencd@0 39 vmime::shared_ptr <vmime::message> msg = vmime::make_shared <vmime::message>();
ferencd@0 40 msg->parse(data);
ferencd@0 41
ferencd@0 42 vmime::shared_ptr <vmime::header> hdr = msg->getHeader();
ferencd@0 43 vmime::shared_ptr <vmime::body> bdy = msg->getBody();
ferencd@0 44
ferencd@0 45 // Now, you can extract some of its components
ferencd@0 46 vmime::charset ch(vmime::charsets::UTF_8);
ferencd@0 47
ferencd@0 48 std::cout
ferencd@0 49 << "The subject of the message is: "
ferencd@0 50 << hdr->Subject()->getValue <vmime::text>()->getConvertedText(ch)
ferencd@0 51 << std::endl
ferencd@0 52 << "It was sent by: "
ferencd@0 53 << hdr->From()->getValue <vmime::mailbox>()->getName().getConvertedText(ch)
ferencd@0 54 << " (email: " << hdr->From()->getValue <vmime::mailbox>()->getEmail() << ")"
ferencd@0 55 << std::endl;
ferencd@0 56 \end{lstlisting}
ferencd@0 57
ferencd@0 58 The output of this program is:
ferencd@0 59
ferencd@0 60 \begin{verbatim}
ferencd@0 61 The subject of the message is: Hello from VMime!
ferencd@0 62 It was sent by: Vincent (email: vincent@vmime.org)
ferencd@0 63 \end{verbatim}
ferencd@0 64
ferencd@0 65
ferencd@0 66 \subsection{Using the {\vcode vmime::messageParser} object} % ----------------
ferencd@0 67
ferencd@0 68 The {\vcode vmime::messageParser} object allows to parse messages in a more
ferencd@0 69 simple manner. You can obtain all the text parts and attachments as well as
ferencd@0 70 basic fields (expeditor, recipients, subject...), without dealing with
ferencd@0 71 MIME message structure.
ferencd@0 72
ferencd@0 73 \begin{lstlisting}[caption={Using {\vcode vmime::messageParser} to parse
ferencd@0 74 more complex messages}]
ferencd@0 75 // Read data from file
ferencd@0 76 std::ifstream file;
ferencd@0 77 file.open("hello.eml", std::ios::in | std::ios::binary);
ferencd@0 78
ferencd@0 79 vmime::utility::inputStreamAdapter is(file);
ferencd@0 80
ferencd@0 81 vmime::string data;
ferencd@0 82 vmime::utility::outputStreamStringAdapter os(data);
ferencd@0 83
ferencd@0 84 vmime::utility::bufferedStreamCopy(is, os);
ferencd@0 85
ferencd@0 86 // Actually parse the message
ferencd@0 87 vmime::shared_ptr <vmime::message> msg = vmime::make_shared <vmime::message>();
ferencd@0 88 msg->parse(data);
ferencd@0 89
ferencd@0 90 // Here start the differences with the previous example
ferencd@0 91 vmime::messageParser mp(msg);
ferencd@0 92
ferencd@0 93 // Output information about attachments
ferencd@0 94 std::cout << "Message has " << mp.getAttachmentCount()
ferencd@0 95 << " attachment(s)" << std::endl;
ferencd@0 96
ferencd@0 97 for (int i = 0 ; i < mp.getAttachmentCount() ; ++i)
ferencd@0 98 {
ferencd@0 99 vmime::shared_ptr <const vmime::attachment> att = mp.getAttachmentAt(i);
ferencd@0 100 std::cout << " - " << att->getType().generate() << std::endl;
ferencd@0 101 }
ferencd@0 102
ferencd@0 103 // Output information about text parts
ferencd@0 104 std::cout << "Message has " << mp.getTextPartCount()
ferencd@0 105 << " text part(s)" << std::endl;
ferencd@0 106
ferencd@0 107 for (int i = 0 ; i < mp.getTextPartCount() ; ++i)
ferencd@0 108 {
ferencd@0 109 vmime::shared_ptr <const vmime::textPart> tp = mp.getTextPartAt(i);
ferencd@0 110
ferencd@0 111 // text/html
ferencd@0 112 if (tp->getType().getSubType() == vmime::mediaTypes::TEXT_HTML)
ferencd@0 113 {
ferencd@0 114 vmime::shared_ptr <const vmime::htmlTextPart> htp =
ferencd@0 115 vmime::dynamicCast <const vmime::htmlTextPart>(tp);
ferencd@0 116
ferencd@0 117 // HTML text is in tp->getText()
ferencd@0 118 // Plain text is in tp->getPlainText()
ferencd@0 119
ferencd@0 120 // Enumerate embedded objects
ferencd@0 121 for (int j = 0 ; j < htp->getObjectCount() ; ++j)
ferencd@0 122 {
ferencd@0 123 vmime::shared_ptr <const vmime::htmlTextPart::embeddedObject> obj =
ferencd@0 124 htp->getObjectAt(j);
ferencd@0 125
ferencd@0 126 // Identifier (Content-Id or Content-Location) is obj->getId()
ferencd@0 127 // Object data is in obj->getData()
ferencd@0 128 }
ferencd@0 129 }
ferencd@0 130 // text/plain or anything else
ferencd@0 131 else
ferencd@0 132 {
ferencd@0 133 // Text is in tp->getText()
ferencd@0 134 }
ferencd@0 135 }
ferencd@0 136 \end{lstlisting}
ferencd@0 137
ferencd@0 138
ferencd@0 139 % ============================================================================
ferencd@0 140 \section{Building messages}
ferencd@0 141
ferencd@0 142 \subsection{A simple message\label{msg-building-simple-message}} % -----------
ferencd@0 143
ferencd@0 144 Of course, you can build a MIME message from scratch by creating the various
ferencd@0 145 objects that compose it (parts, fields, etc.). The following is an example of
ferencd@0 146 how to achieve it:
ferencd@0 147
ferencd@0 148 \begin{lstlisting}[caption={Building a simple message from scratch}]
ferencd@0 149 vmime::shared_ptr <vmime::message> msg = vmime::make_shared <vmime::message>();
ferencd@0 150
ferencd@0 151 vmime::shared_ptr <vmime::header> hdr = msg->getHeader();
ferencd@0 152 vmime::shared_ptr <vmime::body> bdy = msg->getBody();
ferencd@0 153
ferencd@0 154 vmime::shared_ptr <vmime::headerFieldFactory> hfFactory =
ferencd@0 155 vmime::headerFieldFactory::getInstance();
ferencd@0 156
ferencd@0 157 // Append a 'Date:' field
ferencd@0 158 vmime::shared_ptr <vmime::headerField> dateField =
ferencd@0 159 hfFactory->create(vmime::fields::DATE);
ferencd@0 160
ferencd@0 161 dateField->setValue(vmime::datetime::now());
ferencd@0 162 hdr->appendField(dateField);
ferencd@0 163
ferencd@0 164 // Append a 'Subject:' field
ferencd@0 165 vmime::shared_ptr <vmime::headerField> subjectField =
ferencd@0 166 hfFactory->create(vmime::fields::SUBJECT);
ferencd@0 167
ferencd@0 168 subjectField->setValue(vmime::text("Message subject"));
ferencd@0 169 hdr->appendField(subjectField);
ferencd@0 170
ferencd@0 171 // Append a 'From:' field
ferencd@0 172 vmime::shared_ptr <vmime::headerField> fromField =
ferencd@0 173 hfFactory->create(vmime::fields::FROM);
ferencd@0 174
ferencd@0 175 fromField->setValue
ferencd@0 176 (vmime::make_shared <vmime::mailbox>("me@vmime.org"));
ferencd@0 177 hdr->appendField(fromField);
ferencd@0 178
ferencd@0 179 // Append a 'To:' field
ferencd@0 180 vmime::shared_ptr <vmime::headerField> toField =
ferencd@0 181 hfFactory->create(vmime::fields::TO);
ferencd@0 182
ferencd@0 183 vmime::shared_ptr <vmime::mailboxList> recipients =
ferencd@0 184 vmime::make_shared <vmime::mailboxList>();
ferencd@0 185
ferencd@0 186 recipients->appendMailbox
ferencd@0 187 (vmime::make_shared <vmime::mailbox>("you@vmime.org"));
ferencd@0 188
ferencd@0 189 toField->setValue(recipients);
ferencd@0 190 hdr->appendField(toField);
ferencd@0 191
ferencd@0 192 // Set the body contents
ferencd@0 193 bdy->setContents(vmime::make_shared <vmime::stringContentHandler>
ferencd@0 194 ("This is the text of your message..."));
ferencd@0 195
ferencd@0 196 // Output raw message data to standard output
ferencd@0 197 vmime::utility::outputStreamAdapter out(std::cout);
ferencd@0 198 msg->generate(out);
ferencd@0 199 \end{lstlisting}
ferencd@0 200
ferencd@0 201 As you can see, this is a little fastidious. Hopefully, VMime also offers a
ferencd@0 202 more simple way for creating messages. The {\vcode vmime::messageBuilder}
ferencd@0 203 object can create basic messages that you can then customize.
ferencd@0 204
ferencd@0 205 The following code can be used to build exactly the same message as in the
ferencd@0 206 previous example, using the {\vcode vmime::messageBuilder} object:
ferencd@0 207
ferencd@0 208 \begin{lstlisting}[caption={Building a simple message
ferencd@0 209 using {\vcode vmime::messageBuilder}}]
ferencd@0 210 try
ferencd@0 211 {
ferencd@0 212 vmime::messageBuilder mb;
ferencd@0 213
ferencd@0 214 // Fill in some header fields and message body
ferencd@0 215 mb.setSubject(vmime::text("Message subject"));
ferencd@0 216 mb.setExpeditor(vmime::mailbox("me@vmime.org"));
ferencd@0 217 mb.getRecipients().appendAddress
ferencd@0 218 (vmime::make_shared <vmime::mailbox>("you@vmime.org"));
ferencd@0 219
ferencd@0 220 mb.getTextPart()->setCharset(vmime::charsets::ISO8859_15);
ferencd@0 221 mb.getTextPart()->setText(vmime::make_shared <vmime::stringContentHandler>
ferencd@0 222 ("This is the text of your message..."));
ferencd@0 223
ferencd@0 224 // Message construction
ferencd@0 225 vmime::shared_ptr <vmime::message> msg = mb.construct();
ferencd@0 226
ferencd@0 227 // Output raw message data to standard output
ferencd@0 228 vmime::utility::outputStreamAdapter out(std::cout);
ferencd@0 229 msg->generate(out);
ferencd@0 230 }
ferencd@0 231 // VMime exception
ferencd@0 232 catch (vmime::exception& e)
ferencd@0 233 {
ferencd@0 234 std::cerr << "vmime::exception: " << e.what() << std::endl;
ferencd@0 235 }
ferencd@0 236 // Standard exception
ferencd@0 237 catch (std::exception& e)
ferencd@0 238 {
ferencd@0 239 std::cerr << "std::exception: " << e.what() << std::endl;
ferencd@0 240 }
ferencd@0 241 \end{lstlisting}
ferencd@0 242
ferencd@0 243
ferencd@0 244 \subsection{Adding an attachment} % ------------------------------------------
ferencd@0 245
ferencd@0 246 Dealing with attachments is quite simple. Add the following code to the
ferencd@0 247 previous example to attach a file to the message:
ferencd@0 248
ferencd@0 249 \begin{lstlisting}[caption={Building a message with an attachment using
ferencd@0 250 {\vcode vmime::messageBuilder}}]
ferencd@0 251 // Create an attachment
ferencd@0 252 vmime::shared_ptr <vmime::fileAttachment> att =
ferencd@0 253 vmime::make_shared <vmime::fileAttachment>
ferencd@0 254 (
ferencd@0 255 /* full path to file */ "/home/vincent/paris.jpg",
ferencd@0 256 /* content type */ vmime::mediaType("image/jpeg),
ferencd@0 257 /* description */ vmime::text("My holidays in Paris")
ferencd@0 258 );
ferencd@0 259
ferencd@0 260 // You can also set some infos about the file
ferencd@0 261 att->getFileInfo().setFilename("paris.jpg");
ferencd@0 262 att->getFileInfo().setCreationDate
ferencd@0 263 (vmime::datetime("30 Apr 2003 14:30:00 +0200"));
ferencd@0 264
ferencd@0 265 // Add this attachment to the message
ferencd@0 266 mb.appendAttachment(att);
ferencd@0 267 \end{lstlisting}
ferencd@0 268
ferencd@0 269
ferencd@0 270 \subsection{HTML messages and embedded objects} % ----------------------------
ferencd@0 271
ferencd@0 272 VMime also supports aggregate messages, which permits to build MIME messages
ferencd@0 273 containing HTML text and embedded objects (such as images). For more information
ferencd@0 274 about aggregate messages, please read RFC-2557 (\emph{MIME Encapsulation of
ferencd@0 275 Aggregate Documents, such as HTML}).
ferencd@0 276
ferencd@0 277 Creating such messages is quite easy, using the {\vcode vmime::messageBuilder}
ferencd@0 278 object. The following code constructs a message containing text in both plain
ferencd@0 279 and HTML format, and a JPEG image:
ferencd@0 280
ferencd@0 281 \begin{lstlisting}[caption={Building an HTML message with an embedded image
ferencd@0 282 using the {\vcode vmime::messageBuilder}}]
ferencd@0 283 // Fill in some header fields
ferencd@0 284 mb.setSubject(vmime::text("An HTML message"));
ferencd@0 285 mb.setExpeditor(vmime::mailbox("me@vmime.org"));
ferencd@0 286 mb.getRecipients().appendAddress
ferencd@0 287 (vmime::make_shared <vmime::mailbox>("you@vmime.org"));
ferencd@0 288
ferencd@0 289 // Set the content-type to "text/html": a text part factory must be
ferencd@0 290 // available for the type you are using. The following code will make
ferencd@0 291 // the message builder construct the two text parts.
ferencd@0 292 mb.constructTextPart(vmime::mediaType
ferencd@0 293 (vmime::mediaTypes::TEXT, vmime::mediaTypes::TEXT_HTML));
ferencd@0 294
ferencd@0 295 // Set contents of the text parts; the message is available in two formats:
ferencd@0 296 // HTML and plain text. The HTML format also includes an embedded image.
ferencd@0 297 vmime::shared_ptr <vmime::htmlTextPart> textPart =
ferencd@0 298 vmime::dynamicCast <vmime::htmlTextPart>(mb.getTextPart());
ferencd@0 299
ferencd@0 300 // -- Add the JPEG image (the returned identifier is used to identify the
ferencd@0 301 // -- embedded object in the HTML text, the famous "CID", or "Content-Id").
ferencd@0 302 // -- Note: you can also read data from a file; see the next example.
ferencd@0 303 const vmime::string id = textPart->addObject("<...image data...>",
ferencd@0 304 vmime::mediaType(vmime::mediaTypes::IMAGE, vmime::mediaTypes::IMAGE_JPEG));
ferencd@0 305
ferencd@0 306 // -- Set the text
ferencd@0 307 textPart->setCharset(vmime::charsets::ISO8859_15);
ferencd@0 308
ferencd@0 309 textPart->setText(vmime::make_shared <vmime::stringContentHandler>
ferencd@0 310 ("This is the <b>HTML text</b>, and the image:<br/>"
ferencd@0 311 "<img src=\"") + id + vmime::string("\"/>"));
ferencd@0 312
ferencd@0 313 textPart->setPlainText(vmime::make_shared <vmime::stringContentHandler>
ferencd@0 314 ("This is the plain text."));
ferencd@0 315 \end{lstlisting}
ferencd@0 316
ferencd@0 317 This will create a message having the following structure:
ferencd@0 318
ferencd@0 319 \begin{verbatim}
ferencd@0 320 multipart/alternative
ferencd@0 321 text/plain
ferencd@0 322 multipart/related
ferencd@0 323 text/html
ferencd@0 324 image/jpeg
ferencd@0 325 \end{verbatim}
ferencd@0 326
ferencd@0 327 You can easily tell VMime to read the embedded object data from a file. The
ferencd@0 328 following code opens the file \emph{/path/to/image.jpg}, connects it to an
ferencd@0 329 input stream, then add an embedded object:
ferencd@0 330
ferencd@0 331 \begin{lstlisting}
ferencd@0 332 vmime::utility::fileSystemFactory* fs =
ferencd@0 333 vmime::platform::getHandler()->getFileSystemFactory();
ferencd@0 334
ferencd@0 335 vmime::shared_ptr <vmime::utility::file> imageFile =
ferencd@0 336 fs->create(fs->stringToPath("/path/to/image.jpg"));
ferencd@0 337
ferencd@0 338 vmime::shared_ptr <vmime::contentHandler> imageCts =
ferencd@0 339 vmime::make_shared <vmime::streamContentHandler>
ferencd@0 340 (imageFile->getFileReader()->getInputStream(), imageFile->getLength());
ferencd@0 341
ferencd@0 342 const vmime::string cid = textPart.addObject(imageCts,
ferencd@0 343 vmime::mediaType(vmime::mediaTypes::IMAGE, vmime::mediaTypes::IMAGE_JPEG));
ferencd@0 344 \end{lstlisting}
ferencd@0 345
ferencd@0 346
ferencd@0 347 % ============================================================================
ferencd@0 348 \section{Working with attachments: the attachment helper}
ferencd@0 349
ferencd@0 350 The {\vcode attachmentHelper} object allows listing all attachments in a
ferencd@0 351 message, as well as adding new attachments, without using the
ferencd@0 352 {\vcode messageParser} and {\vcode messageBuilders} objects. It can work
ferencd@0 353 directly on messages and body parts.
ferencd@0 354
ferencd@0 355 To use it, you do not need any knowledge about how attachment parts should
ferencd@0 356 be organized in a MIME message.
ferencd@0 357
ferencd@0 358 The following code snippet tests if a body part is an attachment, and if so,
ferencd@0 359 extract its contents to the standard output:
ferencd@0 360
ferencd@0 361 \begin{lstlisting}[caption={Testing if a body part is an attachment}]
ferencd@0 362 vmime::shared_ptr <vmime::bodyPart> part; // suppose we have a body part
ferencd@0 363
ferencd@0 364 if (vmime::attachmentHelper::isBodyPartAnAttachment(part))
ferencd@0 365 {
ferencd@0 366 // The body part contains an attachment, get it
ferencd@0 367 vmime::shared_ptr <const vmime::attachment> attach =
ferencd@0 368 attachmentHelper::getBodyPartAttachment(part);
ferencd@0 369
ferencd@0 370 // Extract attachment data to standard output
ferencd@0 371 vmime::utility::outputStreamAdapter out(std::cout);
ferencd@0 372 attach->getData()->extract(out);
ferencd@0 373 }
ferencd@0 374 \end{lstlisting}
ferencd@0 375
ferencd@0 376 You can also easily extract all attachments from a message:
ferencd@0 377
ferencd@0 378 \begin{lstlisting}[caption={Extracting all attachments from a message}]
ferencd@0 379 vmime::shared_ptr <vmime::message> msg; // suppose we have a message
ferencd@0 380
ferencd@0 381 const std::vector <ref <const attachment> > atts =
ferencd@0 382 attachmentHelper::findAttachmentsInMessage(msg);
ferencd@0 383 \end{lstlisting}
ferencd@0 384
ferencd@0 385 Finally, the {\vcode attachmentHelper} object can be used to add an
ferencd@0 386 attachment to an existing message, whatever it contains (text parts,
ferencd@0 387 attachments, ...). The algorithm can modify the structure of the
ferencd@0 388 message if needed (eg. add a \emph{multipart/mixed} part if no one
ferencd@0 389 exists in the message). Simply call the {\vcode addAttachment}
ferencd@0 390 function:
ferencd@0 391
ferencd@0 392 \begin{lstlisting}[caption={Adding an attachment to an existing message}]
ferencd@0 393 vmime::shared_ptr <vmime::message> msg; // suppose we have a message
ferencd@0 394
ferencd@0 395 // Create an attachment
ferencd@0 396 vmime::shared_ptr <vmime::fileAttachment> att =
ferencd@0 397 vmime::make_shared <vmime::fileAttachment>
ferencd@0 398 (
ferencd@0 399 /* full path to file */ "/home/vincent/paris.jpg",
ferencd@0 400 /* content type */ vmime::mediaType("image/jpeg),
ferencd@0 401 /* description */ vmime::text("My holidays in Paris")
ferencd@0 402 );
ferencd@0 403
ferencd@0 404 // Attach it to the message
ferencd@0 405 vmime::attachmentHelper::addAttachment(msg, att);
ferencd@0 406 \end{lstlisting}
ferencd@0 407