Vmime Book
Vmime Book
Vmime Book
Vincent Richard
vincent@vincent-richard.net
1 Introduction 5
1.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Getting Started 11
3.2 If you can not (or do not want to) use pkg-config . . . . . . . . . . . . . . . . 12
4 Basics 14
4.1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1
4.3.1 The component class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.5 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.9 Encodings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2
5.2.1 A simple message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.5.5 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3
Listings 64
List of figures 65
List of tables 66
4
Chapter 1
Introduction
1.1 Overview
VMime is a powerful C++ class library for working with MIME messages and Internet messaging
services like IMAP, POP or SMTP.
With VMime you can parse, generate and modify messages, and also connect to store and
transport services to receive or send messages over the Internet. The library offers all the features
to build a complete mail client.
• maximum portability.
1.2 Features
MIME features:
5
• 8-bit MIME (RFC-2047)
• Attachments
Network features:
• SASL authentication
VMime library is Free Software and is licensed under the terms of the GNU General Public
License1 (GPL):
1
See Appendix A and http://www.gnu.org/copyleft/gpl.html
6
This document is released under the terms of the GNU Free Documentation License2 (FDL):
2
See http://www.gnu.org/copyleft/fdl.html
7
Chapter 2
2.1 Introduction
If no pre-build packages of VMime is available for your system, or if for some reason you want
to compile it yourself from scratch, this section will guide you through the process.
To build VMime from the sources, you will need the following:
• a working C++ compiler with good STL implementation and also a good support for
templates (for example, GNU GCC) ;
• the GNU TLS Library if you want SSL and TLS2 support ;
You can download a package containing the source files of the latest release of the VMime library
from the VMime web site.
1
Simple Authentication and Security Layer
2
Transport Layer Security
8
You can also obtain the current development version from the CVS. VMime’s CVS repository
can be checked out from anonymous CVS with the following instructions. When prompted for
a password for anonymous, simply press Enter key.
There are two possibilities for compiling VMime: using SCons building system, or using the
Autotools.
note: MS Visual C++ users, you can skip this section as a project file is provided in the
distribution tarball.
SCons is only used for development purposes, and so it is recommended that you use Auto-
tools to build the project as it is a more portable solution, and is likely to work out-of-the-box
on your computer. Thus, we will not describe the process of compiling VMime using SCons
here.
note: Windows users, you can use MinGW and MSYS3 to install a POSIX compatible envi-
ronment on top of Windows. This allow executing configure scripts and Makefiles on Windows.
Before compiling VMime, you should run the configure script to detect some parameters
specific to your platform. Go into the directory where you extracted the tarball and type:
$ ./configure
This will create a file named config.hpp in the vmime/ directory, with the parameters
detected for your platform. You should modify this file only if you know what you are doing!
If you want to enable or disable some features in VMime, you can obtain some help by typing
./configure --help. The defaults should be OK though.
$ make
Please wait a few minutes will the compilation runs (you should have some time to have
a coffee right now!). If you get errors during the compilation, be sure your system meet the
requirements given at the beginning of the chapter. You can also try to get a newer version
(from the CVS, for example) or to get some help on VMime user forums.
3
See on the MinGW website: http://www.mingw.org/
9
If everything has been compiled successfully, you can install the library and the development
files on your system:
# make install
note: you must do that with superuser rights (root) if you chose to install the library into
the default location (ie: /usr/lib and /usr/include).
Now, you are done! You can jump to the next chapter to know how to use VMime in your
program...
10
Chapter 3
Getting Started
First, make sure you have successfully compiled and installed VMime using the instructions
described in Chapter 2. To use VMime in your program, you simply have to include VMime
headers:
#include <vmime/vmime.hpp>
As of version 0.6.1, VMime uses pkg-config to simplify compiling and linking with VMime.
The pkg-config utility is used to detect the appropriate compiler and linker flags needed for
a library.
note: it is highly recommended that you link your program against the shared version of
the library.
All VMime classes and global functions are defined in the namespace vmime, so prefix ex-
plicitely all your declarations which use VMime with vmime::, or import the vmime namespace
into the global namespace with the C++ keywork using (not recommended, though).
11
3.2 If you can not (or do not want to) use pkg-config
Linking with the shared library (.so): compile your program with the -lvmime flag. You
can use the -L path flag if the library file is not in a standard path (ie. not in /usr/lib or
/usr/local/lib).
note: if you want to link your program with the shared version of VMime library, make sure
the library has been compiled using the autotools version of the build system (./configure,
make and make install). When you compile with SCons, only the static library is built and
installed.
Linking with the static library (.a): follow the same procedure as for shared linking
and append the flag -static to force static linking. Although static linking is possible, you are
encouraged to use the shared (dynamic) version of the library.
While the most part of VMime code is pure ANSI C++, there are some features that are
platform-specific: file management (opening/reading/writing files), network code (socket, DNS
resolution) and time management. All the non-portable stuff is done by a bridge object called
a platform handler (see vmime::platform).
At the beginning of your program (before using any VMime object, or calling any VMime
global function), you should tell VMime which platform handler you want to use.
So, if your platform is POSIX, your program should look like this:
#include <vmime/vmime.hpp>
#include <vmime/platforms/posix/posixHandler .hpp>
int main()
{
vmime: : platform : :
setHandler <vmime: : platforms : : posix : : posixHandler>();
12
For using VMime on Windows, include <vmime/platforms/windows/windowsHandler.hpp>
and use the following line to initialize the platform handler:
vmime: : platform : :
setHandler <vmime: : platforms : : windows : : windowsHandler>();
13
Chapter 4
Basics
4.1.1 Introduction
Since version 0.7.2cvs, VMime use smart pointers to simplify memory management. VMime’s
smart pointer implementation relies on RAII1 so that we do not need to bother with deleting
an object (freeing memory) when it is not used anymore.
There are two possibilities for owning a reference to an object. We can own a strong reference
to an object: as long as we keep this reference, the object is not destroyed. Or we can own a
weak reference to the object: the object can be destroyed if nobody owns a strong reference to
it, in which case the weak reference becomes invalid.
An object is destroyed as soon as the last strong reference to it is released. At the same tine,
all weak references (if any) are automatically set to point to NULL.
In VMime, these two types of references are known as vmime::ref and vmime::weak ref,
respectively.
In VMime, all objects that support reference counting inherit from the vmime::object class,
which is responsible for incrementing/decrementing the counter and managing the object’s life
cycle. If you want to create a smart pointer to a new object instance, you should use the function
vmime::create instead of the new operator.
1
Ressource Allocation is Initialisation
14
public :
void sayHello ()
{
std : : cout << ”Hello ” << m name << std : : endl ;
}
private :
int main()
{
vmime: : ref <myObject> obj =
vmime: : create <myObject>(”world” ) ;
obj−>sayHello ( ) ;
return 0;
Smart pointers are copiable, assignable and comparable. You can use them like you would use
normal (”raw”) C++ pointers (eg. you can write !ptr, ptr != NULL, ptr−>method(), ∗ptr...).
Type safety is also guaranteed, and you can type cast smart pointers using the
staticCast(), dynamicCast() and constCast() equivalents on vmime::ref and
vmime::weak ref objects:
// Implicit downcast
15
vmime: : ref <myBase> base = obj ;
// Explicit upcast
vmime: : ref <myObject> obj2 = base .dynamicCast <myObject>();
Weak references are used to resolve reference cycles (an object which refers directly or indi-
rectly to itself). The following example illustrates a typical problem of reference counting:
private :
private :
int main()
{
vmime: : ref <parent> p = vmime: : create <parent>();
vmime: : ref <child> c = vmime: : create <child >();
p−>setChild(c ) ;
}
16
In this example, neither p nor c will be deleted when exiting main(). That’s because p
indirectly points to itself via c, and vice versa. The solution is to use a weak reference to the
parent:
The decision to make the parent or the child a weak reference is purely semantic, and it
depends on the context and the relationships between the objects. Note that when the parent
is deleted, the m parent member of the child points to NULL.
In VMime, error handling is exclusively based on exceptions, there is no error codes, or things
like that.
VMime code may throw exceptions in many different situations: an unexpected error oc-
cured, an operation is not supported, etc. You should catch them if you want to report failures
to the user. This is also useful when debugging your program.
VMime exceptions support chaining: an exception can be encapsulated into another excep-
tion to hide implementation details. The function exception::other() returns the next
exception in the chain, or NULL.
Following is an example code for catching VMime exceptions and writing error messages to
the console:
return os ;
}
...
try
2
http://en.wikipedia.org/wiki/Reference counting
17
{
// . . . some c a l l to VMime. . .
}
catch (vmime: : exception& e)
{
std : : cerr << e ; // VMime exception
}
catch (std : : exception& e)
{
std : : cerr << e .what( ) ; // standard exception
}
Read the source of example6 if yo want to see a more complete example of using
VMime exceptions (such as getting more detailed information by using specialized classes of
vmime::exception).
In VMime, all the components of a message inherit from the same class component. This
includes the message itself (classes message and bodyPart), the header, the header fields and
the value of each header field, the body and all the parts in the message.
The class component provide a common interface for parsing or generating all these com-
ponents (methods parse() and generate()). It also provides additional functions to get
some information about the parsing process or the structure (methods getParsedOffset(),
getParsedLength() and getChildComponents()).
Vmime also provides a set of classes corresponding to the basic types found in a message;
for example a mailbox, a mailbox list, date/time information, media type, etc. They all inherit
from component too.
Date and time are used in several places in VMime, particularly in header fields (Date, Re-
ceived, ...). VMime fully supports RFC-2822’s date and time specification. The object
vmime::datetime is used to manipulate date and time information, and to parse/generate it
from/to RFC-2822 format.
The following code snippet show various manners of using the vmime::datetime object:
18
// Creating from string in RFC−2822 format
vmime: : datetime d1(”Sat , 08 Oct 2005 14:07:52 +0200” ) ;
In MIME, the nature of the data contained in parts is identified using a media type. A gen-
eral type (eg. image) and a sub-type (eg. jpeg) are put together to form a media type (eg.
image/jpeg). This is also called the MIME type.
There are a lot of media types officially registered, and vendor-specific types are possible
(they start with “x-”, eg. application/x-zip-compressed ).
In VMime, the object vmime::mediaType represents a media type. There are also some
constants for top-level types and sub-types in the vmime::mediaTypes namespace. For ex-
ample, you can instanciate a new media type with:
VMime provides several objects for working with mailboxes and addresses.
3
http://www.faqs.org/rfcs/rfc2046.html
19
The vmime::address class is an abstract type for representing an address: it can be either
a mailbox (type vmime::mailbox) or a mailbox group (type vmime::mailboxGroup). A
mailbox is composed of an email address (mandatory) and possibly a name. A mailbox group
is simply a named list of mailboxes (see Figure 4.1).
A MIME message is a recursive structure in which each part can contains one or more parts (or
entities). Each part is composed of a header and a body (actual contents). Figure 4.2 shows
how this model is implemented in VMime, and all classes that take part in it.
20
Figure 4.2: Overall structure of MIME messages
21
4.4.2 Header and header fields
Header fields carry information about a message (or a part) and its contents. Each header
field has a name and a value. All types that can be used as a field value inherit from the
headerFieldValue class.
You cannot instanciate header fields directly using their constructor. Instead, you should use
the headerFieldFactory object. This ensures the right field type and value type is used for
the specified field name. For more information about how to use header fields and the factory,
see section 5.2.1.
Some standard fields are officially registered and have their value type specified in a RFC.
Table 4.4.2.1 lists all the fields registered by default in VMime and the value type they contains.
22
4.4.2.2 Parameterized fields
In addition to a value, some header fields can contain one or more name=value couples which are
called parameters. For example, this is used in the Content-Type field to give more information
about the content:
Some fields provide easy access to their standard parameters (see Table 4.4.2.2). This avoids
finding the parameter and dynamic-casting its value to the right type. The following code
illustrates how to use it:
23
note: In both cases, an exception no such parameter can be thrown if the parameter
does not exist, so be sure to catch it.
4.5 Streams
Streams permit reading or writing data whatever the underlying system is: a file on a hard disk,
a socket connected to a remote service...
There are two types of streams: input streams (from which you can read data) and output
streams (in which you can write data). Some adapters are provided for compatibility and
convenience, for example:
The following example shows two ways of writing the current date to the standard output,
using stream adapters:
// 1. Using outputStreamAdapter
vmime: : u t i l i t y : : outputStreamAdapter out(std : : cout ) ;
// 2. Using outputStreamStringAdapter
vmime: : string dateStr ;
vmime: : u t i l i t y : : outputStreamStringAdapter outStr(dateStr ) ;
24
std : : cout << ”Current date i s : ” << dateStr << std : : endl ;
The most useful filter in VMime (and probably the only one you will need) is the
charsetFilteredOutputStream, which performs inline conversion of charsets. See 4.7 to
know how to use it.
note: After you have finished to use a filtered output stream, it is important to call flush()
on it to flush the internal buffer. If flush() is not called, not all data may be written to the
underlying stream.
4.6.1 Introduction
Content handlers are an abstraction for data sources. They are currently used when some data
need to be stored for later use (eg. body part contents, attachment data, ...). Data can be
stored encoded or unencoded (for more information about encodings, see 4.9).
You can extract data in a content handler using the extract() method (which automati-
cally decodes data if encoded) or extractRaw() (which extracts data without perfoming any
decoding).
The following example shows how to extract the body text from a message, and writing it
to the standard output with charset conversion:
25
vmime: : ref <vmime: : contentHandler> cts = body−>getContents ( ) ;
Listing 4.8: Using content handlers to extract body text from a message
note: The body contents is extracted “as is”. No charset conversion is performed. See 4.7
to know more about conversion between charsets.
When you are building a message, you may need to instanciate content handlers if you want to
set the contents of a body part. The following code snippet shows how to set the body text of
a part from a string:
Content handlers are also used when creating attachments. The following example illustrates
how to create an attachment from a file:
i f (!∗ fileStream )
// handle error
26
vmime: : ref <contentHandler> data =
vmime: : create <vmime: : streamContentHandler>(dataStream , 0);
You will see later that the vmime::fileAttachment class already encapsulates all the
mechanics to create an attachment from a file.
Quoting from RFC-2278: “ The term ’charset’ is used to refer to a method of converting a
sequence of octets into a sequence of characters.”
With the vmime::charset object, VMime supports conversion between charsets using
the iconv library, which is available on almost all existing platforms. See vmime::charset
and vmime::charsetConverter in the class documentation to know more about charset
conversion.
The following example shows how to convert data in one charset to another charset. The
data is extracted from the body of a message and converted to UTF-8 charset:
cth−>extract ( fout ) ;
27
fout . flush ( ) ; // Very important !
MIME standard defines methods4 for dealing with data which is not 7-bit only (ie. the ASCII
character set), in particular in header fields. For example, the field “Subject:” use this data
type.
VMime is fully compatible with RFC-2047 and provides two objects for manipulating 8-bit
data: vmime::text and vmime::word. A word represents textual information encoded in a
specified charset. A text is composed of one or more words.
RFC-2047 describes the process of encoding 8-bit data into a 7-bit form; basically, it relies
on Base64 and Quoted-Printable encoding. Hopefully, all the encoding/decoding process is done
internally by VMime, so creating text objects is fairly simple:
In general, you will not need to decode RFC-2047-encoded data as the pro-
cess is totally transparent in VMime. If you really have to, you can use the
vmime::text::decodeAndUnfold() static method to create a text object from encoded
data.
4
See RFC-2047: Message Header Extensions for Non-ASCII Text
28
You can simply decode it using the following code:
vmime::text also provides a function to convert all the words to another charset in a single
call. The following example shows how to convert text stored in the Subject field of a message:
4.9 Encodings
4.9.1 Introduction
The MIME standard defines a certain number of encodings to allow data to be safely trans-
mitted from one peer to another. VMime provides data encoding and decoding using the
vmime::encoder object.
You should not need to use encoders directly, as all encoding/decoding process is handled
internally by the library, but it is good to know they exist and how they work.
You can create an instance of an encoder using the ’vmime::encoderFactory’ object, giving the
encoding name (base64, quoted-printable, ...). The following example creates an instance of the
Base64 encoder to encode some data:
29
vmime: : string inString (”Some data to encode” ) ;
vmime: : u t i l i t y : : inputStreamStringAdapter in ( inString ) ;
enc−>encode(in , out ) ;
std : : cout << ”Encoded data i s : ” << outString << std : : endl ;
The behaviour of the encoders can be configured using properties. However, not all encoders
support properties. The following example5 enumerates available encoders and the supported
properties for each of them:
5
This is an excerpt from example6
30
4.10 Progress listeners
Progress listeners are used with objects that can notify you about the state of progress when
they are performing an operation.
start() and stop() are called at the beginning and the end of the operation, respectively.
progress() is called each time the status of progress changes (eg. a chunk of data has been
processed). There is no unit specified for the values passed in argument. It depends on the
notifier: it can be bytes, percent, number of messages...
Your listener can return in the cancel() method to cancel the operation. However, be
warned that cancelling is not always supported by the source object.
31
Chapter 5
5.1.1 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”:
The following code snippet shows how you can easily obtain a vmime::message object
from data in this file:
vmime: : u t i l i t y : : inputStreamAdapter i s ( f i l e ) ;
vmime: : u t i l i t y : : bufferedStreamCopy( is , os ) ;
32
// Actually parse the message
vmime: : ref <vmime: : message> msg = vmime: : create <vmime: : message>();
msg−>parse(data ) ;
std : : cout
<< ”The subject of the message i s : ”
<< hdr−>Subject()−>getValue ( ) .dynamicCast <vmime: : text>()−>getDecodedText(ch)
<< std : : endl
<< ”It was sent by: ”
<< hdr−>From()−>getValue ( ) .dynamicCast <vmime: : mailbox>()−>getName( ) . getDecodedText(ch)
<< ” (email : ” << hdr−>From()−>getValue ( ) . getEmail() << ”)”
<< std : : endl ;
The 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.
vmime: : u t i l i t y : : inputStreamAdapter i s ( f i l e ) ;
vmime: : u t i l i t y : : bufferedStreamCopy( is , os ) ;
33
// Actually parse the message
vmime: : ref <vmime: : message> msg = vmime: : create <vmime: : message>();
msg−>parse(data ) ;
// text/html
i f (tp−>getType ( ) .getSubType() == vmime: : mediaTypes : :TEXTHTML)
{
vmime: : ref <const vmime: : htmlTextPart> htp =
tp .dynamicCast <const vmime: : htmlTextPart>();
34
{
// Text i s in tp−>getText ()
}
}
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:
// Append a ’Date: ’ f i e l d
vmime: : ref <vmime: : headerField> dateField =
hfFactory−>create (vmime: : f i e l d s : :DATE) ;
// Append a ’ Subject : ’ f i e l d
vmime: : ref <vmime: : headerField> subjectField =
hfFactory−>create (vmime: : f i e l d s : :SUBJECT) ;
// Append a ’From: ’ f i e l d
vmime: : ref <vmime: : headerField> fromField =
hfFactory−>create (vmime: : f i e l d s : :FROM) ;
fromField−>setValue
(vmime: : create <vmime: : mailbox>(”me@vmime. org” ) ) ;
hdr−>appendField(fromField ) ;
35
// Append a ’To: ’ f i e l d
vmime: : ref <vmime: : headerField> toField =
hfFactory−>create (vmime: : f i e l d s : :TO) ;
recipients−>appendMailbox
(vmime: : create <vmime: : mailbox>(”you@vmime. org” ) ) ;
toField−>setValue( recipients ) ;
hdr−>appendField( toField ) ;
As you can see, this is a little fastidious. Hopefully, VMime also offers a more simple way
for creating messages. The 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 vmime::messageBuilder object:
try
{
vmime: : messageBuilder mb;
// Message construction
vmime: : ref <vmime: : message> msg = mb. construct ( ) ;
36
// Output raw message data to standard output
vmime: : u t i l i t y : : 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 ;
}
Dealing with attachments is quite simple. Add the following code to the previous example to
attach a file to the message:
// Create an attachment
vmime: : ref <vmime: : fileAttachment> att =
vmime: : create <vmime: : fileAttachment>
(
/∗ f u l l path to f i l e ∗/ ”/home/vincent/paris . jpg” ,
/∗ content type ∗/ vmime: : mediaType(”image/jpeg ) ,
/∗ description ∗/ vmime: : text (”My holidays in Paris”)
);
37
5.2.3 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 mes-
sages, please read RFC-2557 (MIME Encapsulation of Aggregate Documents, such as HTML).
Creating such messages is quite easy, using the vmime::messageBuilder object. The
following code constructs a message containing text in both plain and HTML format, and a
JPEG image:
// F i l l in some header f i e l d s
mb. setSubject (vmime: : text (”An HTML message” ) ) ;
mb. setExpeditor(vmime: : mailbox(”me@vmime. org” ) ) ;
mb. getRecipients ( ) . appendAddress
(vmime: : create <vmime: : mailbox>(”you@vmime. org” ) ) ;
// Set contents of the text parts ; the message i s available in two formats :
// HTML and plain text . The HTML format also includes an embedded image .
vmime: : ref <vmime: : htmlTextPart> textPart =
mb. getTextPart ( ) .dynamicCast <vmime: : htmlTextPart>();
Listing 5.6: Building an HTML message with an embedded image using the
vmime::messageBuilder
38
This will create a message having the following structure:
multipart/alternative
text/plain
multipart/related
text/html
image/jpeg
You can easily tell VMime to read the embedded object data from a file. The following code
opens the file /path/to/image.jpg, connects it to an input stream, then add an embedded object:
vmime: : u t i l i t y : : fileSystemFactory∗ f s =
vmime: : platform : : getHandler()−>getFileSystemFactory ( ) ;
The attachmentHelper object allows listing all attachments in a message, as well as adding
new attachments, without using the messageParser and 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:
39
// Extract attachment data to standard output
vmime: : u t i l i t y : : outputStreamAdapter out(std : : cout ) ;
attach−>getData()−>extract (out ) ;
}
// Create an attachment
vmime: : ref <vmime: : fileAttachment> att =
vmime: : create <vmime: : fileAttachment>
(
/∗ f u l l path to f i l e ∗/ ”/home/vincent/paris . jpg” ,
/∗ content type ∗/ vmime: : mediaType(”image/jpeg ) ,
/∗ description ∗/ vmime: : text (”My holidays in Paris”)
);
40
Chapter 6
6.1 Introduction
In addition to parsing and building MIME messages, VMime also offers a lot of features to work
with messaging services. This includes connecting to remote messaging stores (like IMAP or
POP3), local stores (maildir) and transport services (send messages over SMTP or local send-
mail), through an unified interface (see Figure 6.1). That means that you can use independently
IMAP of POP3 without having to change any line of code.
Source code of Example6 covers all features presented in this chapter, so it is important
you take some time to read it.
• vmime::net::store: interface for a store service. A store service offers access to a set
of folders containing messages. This is used for IMAP, POP3 and maildir.
41
Figure 6.1: Overall structure of the messaging module
42
• vmime::net::message: this is the interface for dealing with messages. For a given
message, you can have access to its flags, its MIME structure and you can also extract the
whole message data or given parts (if supported by the underlying protocol).
Sessions are used to store configuration parameters for services. They contains a set of typed
properties that can modify the behaviour of the services. Before using a messaging service, you
must create and initialize a session object:
vmime: : ref <net : : session> theSession = vmime: : create <net : : session >();
• authentication parameters: user credentials required to use the service (if any);
Properties are stored using a dotted notation, to specify the service type, the protocol name,
the category and the name of the property:
{service_type}.{protocol}.category.name
The session properties are stored in a vmime::propertySet object. To set the value of a
property, you can use either:
or:
43
6.2.2 Available properties
Following is a list of available properties and the protocols they apply to, as the time of writing
this documentation1 . For better clarity, the prefixes do not appear in this table.
sendmail
SMTPS
IMAPS
POP3S
maildir
SMTP
IMAP
POP3
Property name Type Description
options.sasl bool Set to true to use SASL • • • • • •
authentication, if avail-
able.
options.sasl.fallback bool Fail if SASL authen- • • • • • •
tication failed (do not
try other authentication
mechanisms).
auth.username2 string Set the username of the • • • • • •
account to connect to.
auth.password?? string Set the password of the • • • • • •
account.
connection.tls bool Set to true to start a • • •
secured connection using
STARTTLS extension, if
available.
connection.tls.required bool Fail if a secured connec- • • •
tion cannot be started.
server.address string Server host name or IP • • • • • •
address.
server.port int Server port. • • • • • •
server.rootpath string Root directory for mail •
repository (eg. /home-
/vincent/Mail ).
1
You can get an up-to-date list of the properties by running Example7
44
These are the protocol-specific options:
You can create a service either by specifying its protocol name, or by specifying the URL of the
service. Creation by name is deprecated so this chapter only presents the latter option.
protocol://[username[:password]@]host[:port]/[root-path]
note: For local services (ie. sendmail and maildir ), the host part is not used, but it must
not be empty (you can use ”localhost”).
45
When you have the connection URL, instanciating the service is quite simple. Depending
on the type of service, you will use either getStore() or getTransport(). For example,
for store services, use:
Some services need some user credentials (eg. username and password) to open a session.
In VMime, user credentials can be specified in the session properties or by using a custom
authenticator (callback).
Although not recommended, you can also specify username and password directly in the con-
nection URL, ie: imap://username:password@imap.example.com/. This works only for services
requiring an username and a password as user credentials, and no other information.
Sometimes, it may not be very convenient to set username/password in the session properties,
or not possible (eg. extended SASL mechanisms) . That’s why VMime offers an alternate way of
getting user credentials: the authenticator object. Basically, an authenticator is an object
that can return user credentials on-demand (like a callback).
Currently, there are two types of authenticator in VMime: a basic authenticator (class
vmime::security::authenticator) and, if SASL support is enabled, a SASL authentica-
tor (class vmime::security::sasl::SASLAuthenticator). Usually, you should use the
default implementations, or at least make your own implementation inherit from them.
The following example shows how to use a custom authenticator to request the user to enter
her/his credentials:
46
{
std : : cout << ”Enter your username: ” << std : : endl ;
return res ;
}
return res ;
}
};
note: An authenticator object should be used with one and only one service at a time. This
is required because the authentication process may need to retrieve the service name (SASL).
Of course, this example is quite simplified. For example, if several authentication mecha-
nisms are tried, the user may be requested to enter the same information multiple times. See
Example6 for a more complex implementation of an authenticator, with caching support.
If you want to use SASL (ie. if options.sasl is set to true), your au-
thenticator must inherit from vmime::security::sasl::SASLAuthenticator or
vmime::security::sasl::defaultSASLAuthenticator, even if you do not use the
SASL-specific methods getAcceptableMechanisms() and setSASLMechanism(). Have
a look at Example6 to see an implementation of an SASL authenticator.
47
class mySASLAuthenticator : public vmime: : security : : sasl : : defaultSASLAuthenticator
{
typedef vmime: : security : : sasl : :SASLMechanism mechanism;
// save us typing
defaultSASLAuthenticator : : setSASLMechanism(mech) ;
}
You have two possibilities for giving message data to the service when you want to send a
message:
• either you have a reference to a message (type vmime::message) and you can simply
call send(msg);
• or you only have raw message data (as a string, for example), and you have to call the
second overload of send(), which takes additional parameters (corresponding to message
48
envelope);
The following example illustrates the use of a transport service to send a message using the
second method:
// Connect i t
tr−>connect ( ) ;
tr−>send(
/∗ expeditor ∗/ from ,
/∗ recipient ( s ) ∗/ to ,
/∗ data ∗/ is ,
/∗ t o t a l length ∗/ msgData. length ( ) ) ;
note: Exceptions can be thrown at any time when using a service. For better clarity,
exceptions are not caught here, but be sure to catch them in your own application to provide
49
error feedback to the user.
If you use SMTP, you can enable authentication by setting some properties on the session
object (service::setProperty() is a shortcut for setting properties on the session with
the correct prefix):
The first basic step for using a store service is to connect to it. The following example shows
how to initialize a session and instanciate the store service:
// Connect i t
store−>connect ( ) ;
note: Example6 contains a more complete example for connecting to a store service, with
support for a custom authenticator.
You can open a folder using two different access modes: either in read-only mode (where you
can only read message flags and contents), or in read-write mode (where you can read messages,
but also delete them or add new ones). When you have a reference to a folder, simply call the
open() method with the desired access mode:
50
note: Not all stores support the read-write mode. By default, if the read-write mode is not
available, the folder silently fall backs on the read-only mode, unless the failIfModeIsNotAvailable
argument to open() is set to true.
Call getDefaultFolder() on the store to obtain a reference to the default folder, which
is usually the INBOX folder (where messages arrive when they are received).
You can also open a specific folder by specifying its path. The following example will open
a folder named bar, which is a child of foo in the root folder:
note: You can specify a path as a string as there is no way to get the separator used to
delimitate path components. Always use operator/= or appendComponent.
note: Path components are of type vmime::word, which means that VMime supports
folder names with extended characters, not only 7-bit US-ASCII. However, be careful that this
may not be supported by the underlying store protocol (IMAP supports it, because it uses
internally a modified UTF-7 encoding).
You can fetch some information about a message without having to download the whole mes-
sage. Moreover, folders support fetching for multiple messages in a single request, for better
performance. The following items are currently available for fetching:
51
note: Not all services support all fetchable items. Call getFetchCapabilities() on a
folder to know which information can be fetched by a service.
The following code shows how to list all the messages in a folder, and retrieve basic infor-
mation to show them to the user:
folder−>fetchMessages(allMessages ,
vmime: : net : : folder : :FETCHFLAGS |
vmime: : net : : folder : :FETCHENVELOPE) ;
To extract the whole contents of a message (including headers), use the extract() method
on a vmime::net::message object. The following example extracts the first message in the
default folder:
52
msg−>extract (out ) ;
Some protocols (like IMAP) also support the extraction of specific MIME parts of a message
without downloading the whole message. This can save bandwidth and time. The method
extractPart() is used in this case:
multipart/mixed
text/html
image/jpeg [*]
The previous example will extract the header and body of the image/jpeg part.
6.5.5 Events
As a result of executing some operation (or from time to time, even if no operation has been
performed), a store service can send events to notify you that something has changed (eg. the
number of messages in a folder). These events may allow you to update the user interface
associated to a message store.
• message change: sent when the number of messages in a folder has changed (ie. some
messages have been added or removed);
• message count change: sent when one or more message(s) have changed (eg. flags or
deleted status);
• folder change: sent when a folder has been created, renamed or deleted.
You can register a listener for each event type by using the corresponding methods on
a folder object: addMessageChangedListener(), addMessageCountListener() or
addFolderListener(). For more information, please read the class documentation for
vmime::net::events namespace.
53
6.6 Handling time-outs
Unexpected errors can occur while messaging services are performing operations and waiting a
response from the server (eg. server stops responding, network link falls down). As all operations
as synchronous, they can be “blocked” a long time before returning (in fact, they loop until they
either receive a response from the server, or the underlying socket system returns an error).
VMime provides a mechanism to control the duration of operations. This mechanism allows
the program to cancel an operation that is currently running.
/∗∗ Called when the time limit has been reached (when
∗ isTimeOut() returned true ) .
∗
∗ @return true to continue (and reset the time−out)
∗ or f a l s e to cancel the current operation
∗/
virtual const bool handleTimeOut() = 0;
};
While the operation runs, the service calls isTimeout() at variable intervals. If the
function returns true, then handleTimeout() is called. If it also returns true, the
operation is cancelled and an operation timed out exception is thrown. The function
resetTimeout() is called each time data has been received from the server to reset time-
out delay.
54
{
return (getTime() >= m last + 30); // 30 seconds time−out
}
void resetTimeOut()
{
m last = getTime( ) ;
}
private :
To make the service use your time-out handler, you need to write a factory class, to allow
the service to create instances of the handler class. This is required because the service can use
several connections to the server simultaneously, and each connection needs its own time-out
handler.
55
}
};
Then, call the setTimeoutHandlerFactory() method on the service object to set the
time-out handler factory to use during the session:
56
6.7 Secured connection using TLS/SSL
6.7.1 Introduction
If you have enabled TLS support in VMime, you can configure messaging services so that they
use a secured connection.
Quoting from RFC-2246 - the TLS 1.0 protocol specification: “ The TLS protocol provides
communications privacy over the Internet. The protocol allows client/server applications to
communicate in a way that is designed to prevent eavesdropping, tampering, or message forgery.”
• privacy: transmission of data between client and server cannot be read by someone in the
middle of the connection;
• integrity: original data which is transferred between a client and a server can not be
modified by an attacker without being detected.
note: What is the difference between SSL and TLS? SSL is a protocol designed by Netscape.
TLS is a standard protocol, and is partly based on version 3 of the SSL protocol. The two
protocols are not interoperable, but TLS does support a mechanism to back down to SSL 3.
• you can connect to a server listening on a special port (eg. IMAPS instead of IMAP): this
is the classical use of SSL, but is now deprecated;
• connect to a server listening on the default port, and then begin a secured connection: this
is STARTTLS.
To use the classical SSL/TLS way, simply use the “S” version of the protocol to connect to the
server (eg. imaps instead of imap). This is currently available for SMTP, POP3 and IMAP.
57
6.7.2.2 Using STARTTLS
To make the service start a secured session using the STARTTLS method, simply set the con-
nection.tls property:
theService−>setProperty(”connection . t l s ” , true ) ;
note: If, for some reason, a secured connection cannot be started, the default behaviour
is to fallback on a normal connection. To make connect() fail if STARTTLS fails, set the
connection.tls.required to true.
If you tried the previous examples, a certificate verification exception might have
been thrown. This is because the default certificate verifier in VMime did not manage to verify
the certificate, and so could not trust it.
Basically, when you connect to a server using TLS, the server responds with a list of certifi-
cates, called a certificate chain (usually, certificates are of type X.5093 ). The certificate chain
is ordered so that the first certificate is the subject certificate, the second is the subject’s issuer
one, the third is the issuer’s issuer, and so on.
To decide whether the server can be trusted or not, you have to verify that each certificate is
valid (ie. is trusted). For more information about X.509 and certificate verification, see related
articles on Wikipedia 4 .
The default certificate verifier maintains a list of root (CAs) and user certificates that are trusted.
By default, the list is empty. So, you have to initialize it before using the verifier.
1. for every certificate in the chain, verify that the certificate has been issued by the next
certificate in the chain;
2. for every certificate in the chain, verify that the certificate is valid at the current time;
3
And VMime currently supports only X.509 certificates
4
See http://wikipedia.org/wiki/Public key certificate
5
See http://wikipedia.org/wiki/Certification path validation algorithm
58
• first, verify that the the last certificate in the chain was issued by a third-party that
we trust (root CAs);
• if the issuer certificate cannot be verified against root CAs, compare the subject’s
certificate against the trusted certificates (the certificates the user has decided to
trust).
i f ( ! certFile )
{
// . . . handle error . . .
}
i f ( cert != NULL)
return cert ;
return cert ;
}
59
// Load root CAs (such as Verisign or Thawte)
std : : vector <vmime: : ref <vmime: : security : : cert : : X509Certificate> > rootCAs;
vrf−>setX509RootCAs(rootCAs) ;
vrf−>setX509TrustedCerts( trusted ) ;
The following example shows how to implement an interactive certificate verifier which relies
on the user’s decision, and nothing else (you SHOULD NOT use this in a production application
as this is obviously a serious security issue):
60
std : : string answer ;
std : : getline (std : : cin , answer ) ;
note: In production code, it may be a good idea to remember user’s decisions about which
certificates to trust and which not. See Example6 for a basic cache implementation.
Finally, to make the service use your own certificate verifier, simply write:
61
Listings
63
5.6 Building an HTML message with an embedded image using the
vmime::messageBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
64
List of Figures
65
List of Tables
66
Appendix A
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation’s software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
67
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author’s protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors’ reputations.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
68
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
69
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
70
restrictions on the recipients’ exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
71
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
<one line to give the program’s name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
72
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
The hypothetical commands ‘show w’ and ‘show c’ should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than ‘show w’ and ‘show c’; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
73