VMime :: a C++ mail library

Sections

Documentation

HOWTOs

Introduction

This section will let you learn VMime by example.
The most part of what you can do with VMime is explained in this page.

Basics

The component class

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 components (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.

The text type

The text type can contain text in multiple charsets. It is composed of one or more words, each with a different charset. For example, the content of the field 'Subject:' is of type text.

You can easily make a text object from a simple string using the static function text::newFromString():

text* text::newFromString(const string& in, const charset& ch, text* generateInExisting = NULL);

The following code illustrates the use of this function:

vmime::string inText = "Linux dans un téléphone mobile";
vmime::charset inCharset = "iso-8859-1";

vmime::text outText;
outText.createFromString(inText, inCharset);

// 'outText' now contains 3 words:
//    . <us-ascii>   "Linux dans un "
//    . <iso-8859-1> "téléphone "
//    . <us-ascii>   "mobile"

or :

vmime::text* myText = vmime::newFromString(inText, inCharset);
// ...
delete (myText);

Catching exceptions

VMime code may throw exceptions in many different situations: an unexpected error occured, an operation is not supported, etc. You should catch them if you want to report failures to the user. This is also useful for debugging your program.

VMime exceptions support chaining: an exception can be encapsulated into another exception to hide implementation details. The exception::other() function returns the next exception in the chain, or NULL.

Following is an example code for catching and showing VMime exceptions to the user:

std::ostream& operator<<(std::ostream& os, const vmime::exception& e)
{
   os << "* vmime::exceptions::" << e.name() << std::endl;
   os << "    what = " << e.what() << std::endl;

   // More information for special exceptions
   if (dynamic_cast <const vmime::exceptions::command_error*>(&e))
   {
      const vmime::exceptions::command_error& cee =
         dynamic_cast <const vmime::exceptions::command_error&>(e);

      os << "    command = " << cee.command() << std::endl;
      os << "    response = " << cee.response() << std::endl;
   }

   if (dynamic_cast <const vmime::exceptions::connection_greeting_error*>(&e))
   {
      const vmime::exceptions::connection_greeting_error& cgee =
         dynamic_cast <const vmime::exceptions::connection_greeting_error&>(e);

      os << "    response = " << cgee.response() << std::endl;
   }

   if (dynamic_cast <const vmime::exceptions::authentication_error*>(&e))
   {
      const vmime::exceptions::authentication_error& aee =
         dynamic_cast <const vmime::exceptions::authentication_error&>(e);

      os << "    response = " << aee.response() << std::endl;
   }

   if (dynamic_cast <const vmime::exceptions::filesystem_exception*>(&e))
   {
      const vmime::exceptions::filesystem_exception& fse =
         dynamic_cast <const vmime::exceptions::filesystem_exception&>(e);

      os << "    path = " << vmime::platformDependant::getHandler()->
         getFileSystemFactory()->pathToString(fse.path()) << std::endl;
   }

   if (e.other() != NULL)
      os << *e.other();

   return os;
}

...

try
{
   // ...some call to VMime...
}
catch (vmime::exception& e)
{
   std::cerr << e;         // VMime exception
}
catch (std::exception& e)
{
   std::cerr << e.what();  // standard exception
}

The 'contentHandler' object

contentHandler object is an abstraction for data source. It is used everywhere VMime needs some data when building a message: body part contents, attachment data, etc.

There are 3 types of content handlers:

Here are some examples of use:

Using the message builder

Building a simple message

The following example use the messageBuilder component to construct a very simple message, with only text (RFC-[2]822 format). Don't forget to include necessary VMime initializations at the beginning of your program!

try
{
    vmime::messageBuilder mb;

    // Fill in some header fields and message body
    mb.setExpeditor(vmime::mailbox("sender@server.com"));
    mb.getRecipients().appendAddress(new vmime::mailbox("recipient@server.com"));
    mb.getBlindCopyRecipients().appendAddress(new vmime::mailbox("recipient-bcc@server.com"));
    mb.setSubject(vmime::text("My first message generated with VMime"));

    mb.getTextPart()->setCharset(vmime::charsets::ISO8859_15);
    mb.getTextPart()->setText
	(vmime::stringContentHandler("I'm writing this short text to test the" \
        	"construction of a message using vmime::messageBuilder component."));

    // Message construction
    vmime::message* msg = mb.construct();

    // Generate text corresponding to the message
    vmime::string dataToSend = msg->generate();

    // ...send the message...

    // Destruct the message object
    delete (msg);
}
// VMime exception
catch (vmime::exception& e)
{
    std::cerr << e;
}
// Standard exception
catch (std::exception& e)
{
    std::cerr << "std::exception: " << e.what() << std::endl;
}

Building a message with an attachment

Let's take the code of the previous example and add some lines of code to include an attachment in your message. The attachment data will be read from a file:

try
{
    vmime::messageBuilder mb;

    // ... code of previous example (before .construct()!)

    // Add an attachment
    vmime::fileAttachment* a = new vmime::fileAttachment
    (
        "/full/path/to/file",                           // full path to file
        vmime::mediaType("application/octet-stream"),   // content type
        vmime::text("My first attachment"))             // description
    );

    // You can set some infos on the file attachment
    a->getFileInfo().setFilename("file_name");
    a->getFileInfo().setCreationDate(vmime::datetime("30 Apr 2003 14:30:00 +0200"));

    mb.appendAttachment(a); // add this attachment to the message

    // ...
}
// VMime exception
catch (vmime::exception& e)
{
    std::cerr << e;
}
// Standard exception
catch (std::exception& e)
{
    std::cerr << "std::exception: " << e.what() << std::endl;
}

Building a message with both HTML and plain text parts

The messageBuilder is a very powerful component. As a proof, see the following code that constructs a message in which the text is available in two formats (HTML and plain), and with an embedded object (an image in the HTML text).

try
{
    vmime::messageBuilder mb;

    // Fill in some header fields
    mb.setExpeditor(vmime::mailbox("sender@server.com"));
    mb.getRecipients().appendAddress(new vmime::mailbox("recipient@server.com"));
    mb.getBlindCopyRecipients().appendAddress(new vmime::mailbox("recipient-bcc@server.com"));
    mb.setSubject(vmime::text("My first message generated with VMime"));

    // 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 the contents of the text parts; the message is available in two format:
    // HTML and plain text. The HTML format also includes an embedded image.
    vmime::htmlTextPart& textPart = dynamic_cast<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")
    vmime::string id = textPart.addObject("<...TODO: image data...>",
         vmime::mediaType(vmime::mediaTypes::IMAGE, vmime::mediaTypes::IMAGE_JPEG));

    // -- set the text
    textPart.setCharset(vmime::charsets::ISO8859_15);
    textPart.setText(vmime::stringContentHandler(vmime::string
	("This is the <b>HTML text</b>, and the image:<br/>" \
         "<img src=\"") + id + vmime::string("\"/>")));
    textPart.setPlainText(vmime::stringContentHandler("This is the plain text."));

    // Message construction
    vmime::message* msg = mb.construct();

    // Generate text corresponding to the message
    vmime::string dataToSend = msg->generate();

    // ...send the message...

    // Destruct the message object
    delete (msg);
}
// VMime exception
catch (vmime::exception& e)
{
    std::cerr << e;
}
// Standard exception
catch (std::exception& e)
{
    std::cerr << "std::exception: " << e.what() << std::endl;
}

Using the message parser

The messageParser is a powerful and simple solution to extract common data from messages: text parts and attachments. Let's see some simple uses...

Extracting text parts from a message

The following example illustrates the use of the messageParser component to enumerate the text parts in a message.

try
{
    vmime::messageParser mp("<...message data...>");

    // Enumerate text parts in the message
    for (int i = 0 ; i < mp.getTextPartCount() ; ++i)
    {
        const vmime::textPart& part = *mp.getTextPartAt(i);

        // Output part content-type
        std::cout << part.type().generate() << std::endl;

        // text/html
        if (part.type().subType() == vmime::mediaTypes::TEXT_HTML)
        {
            const vmime::htmlTextPart& hp = dynamic_cast<const vmime::htmlTextPart&>(part);

            // HTML text is in hp.getText()
            // Plain text is in hp.getPlainText()

            // Enumerate embedded objects (images, etc.)
            for (int j = 0 ; j < hp.getObjectCount() ; ++j)
            {
                const vmime::htmlTextPart::embeddedObject& obj = *hp.getObjectAt(j);

                // Identifier (content-id or content-location) in obj.getId()
                // Data in obj.getData()
            }
        }
        // text/plain
        else
        {
            const vmime::textPart& tp = dynamic_cast<const vmime::textPart&>(part);

            // Text in tp.getText()
        }
    }
}
// VMime exception
catch (vmime::exception& e)
{
    std::cerr << e;
}
// Standard exception
catch (std::exception& e)
{
    std::cerr << "std::exception: " << e.what() << std::endl;
}

Extracting attachments from a message

With the messageParser component, you can also enumerate and extract attachments from a message. The code is very simple:

try
{
    vmime::messageParser mp("<...message data...>");

    // Enumerate attachments in the message
    for (int i = 0 ; i < mp.getAttachmentCount() ; ++i)
    {
        const vmime::attachment& attach = *mp.getAttachmentAt(i);

        // Media type in attach.getType()
        // Description in attach.getDescription()
        // Attachment data in attach.getData()
    }
}
// VMime exception
catch (vmime::exception& e)
{
    std::cerr << e;
}
// Standard exception
catch (std::exception& e)
{
    std::cerr << "std::exception: " << e.what() << std::endl;
}

Using the messaging module

Before starting, you should take a look at the example6 sample program, which demonstrates the use of the messaging module.

Implementing an authentication handler

When connecting to services, you may need to supply a username and a password to the server. In VMime, the authenticator object is responsible for this task.

If you don't provide an authenticator, and if credentials are needed to connect to the server, they will be read from the session properties (see below for knowing about creating a session).

The following code shows an example of how to implement a custom authenticator, by reading the credentials from the standard input:

class my_auth : public vmime::messaging::authenticator
{
    const vmime::messaging::authenticationInfos requestAuthInfos() const
    {
        vmime::string username, password;

        std::cout << "Username: "; std::cout.flush();
        std::cin >> username;

        std::cout << "Password: "; std::cout.flush();
        std::cin >> password;

        return (vmime::messaging::authenticationInfos(username, password));
    }
};

Creating a session

Creating a session is as easy as instanciating an object (the messaging::session object). But you will also have to initialize this object by setting some properties on it:

vmime::messaging::session sess;
sess.getProperties()["store.protocol"] = "imap";
sess.getProperties()["transport.protocol"] = "smtp";

The store.protocol and transport.protocol properties indicate which protocols to use for message store and message transport services, respectively.

Some properties are global to this session, others are specific to one service or to one protocol. For example, you can set properties to enable options for a protocol (eg. enable or disable APOP authentication for POP3).

Enumerating available services

The following code snippet use the serviceFactory object to enumerate the available services (stores and transports), and print their name and their settable properties on the standard output. This is an excerpt from example7.

You can also retrieve the type and the default value of each property. This permits setting service properties in a generic manner.

// Get a reference to the serviceFactory object
vmime::messaging::serviceFactory* sf = vmime::messaging::serviceFactory::getInstance();

std::cout << "Available messaging services:" << std::endl;

for (int i = 0 ; i < sf->getServiceCount() ; ++i)
{
    const vmime::messaging::serviceFactory::registeredService& srv = *sf->getServiceAt(i);

    // Print the service name (protocol) and the default port
    std::cout << "  * " << srv.name()
              << " (" << srv.getInfos().getDefaultPort() << ")" << std::endl;

    // Enumerate possible properties for this service
    std::vector <vmime::string> props = srv.getInfos().getAvailableProperties();

    for (std::vector <vmime::messaging::serviceInfos::property>::const_iterator it = props.begin() ;
         it != props.end() ; ++it)
    {
        const vmime::messaging::serviceInfos::property& p = *it;

        const vmime::string name = serv.getInfos().getPropertyPrefix() + p.getName();

        vmime::string type;

        switch (p.getType())
        {
        case vmime::messaging::serviceInfos::property::TYPE_INTEGER: type = "TYPE_INTEGER"; break;
        case vmime::messaging::serviceInfos::property::TYPE_STRING: type = "TYPE_STRING"; break;
        case vmime::messaging::serviceInfos::property::TYPE_BOOL: type = "TYPE_BOOL"; break;
        default: type = "(unknown)"; break;
        }

        vmime::string flags;

        if (p.getFlags() & vmime::messaging::serviceInfos::property::FLAG_REQUIRED)
            flags += " FLAG_REQUIRED";
        if (p.getFlags() & vmime::messaging::serviceInfos::property::FLAG_HIDDEN)
            flags += " FLAG_HIDDEN";

        std::cout << "      - " << serv.getInfos().getPropertyPrefix() + p.getName();
        std::cout << " (type=" << type << ", flags=" << flags;
        std::cout << ", defaultValue=" << p.getDefaultValue() << ")" << std::endl;
    }
}

Creating services using URLs

Since version 0.6.1, it is possible to get a service instance given an URL. The URL should at least specify the protocol to use and the server address. It is also possible to specify the username and password for the account (if not specified, an authenticator object is used).

Here are some examples:

// Maildir
vmime::messaging::url url("maildir://localhost/home/vincent/Mail");  // Host not used
vmime::messaging::store* store = session.getStore(url);

// IMAP
vmime::messaging::url url("imap://vincent:XXXXX@myserver.org:143/");
vmime::messaging::store* store = session.getStore(url);

// POP3
vmime::messaging::url url("pop3://vincent:XXXXX@myserver.org/");
vmime::messaging::store* store = session.getStore(url);

// SMTP
vmime::messaging::url url("smtp://myserver.org/");
vmime::messaging::transport* transport = session.getTransport(url);

// Sendmail
vmime::messaing::url url("sendmail://localhost/");  // Host not used (always localhost)
vmime::messaging::transport* transport = session.getTransport(url);

Using the transport service

The following example illustates the use of the transport service to send a message over the Internet using the SMTP protocol:

try
{
    // Session initialization (global properties)
    vmime::messaging::session sess;
    sess.getProperties()["transport.protocol"] = "smtp";

    // Transport protocol configuration (service-specific properties)
    vmime::messaging::transport* tr = sess.getTransport();

    // -- set the server address and port (if not specified, default is used)
    sess.getProperties()[tr->getInfos().getPropertyPrefix() + "server.address"] = "smtp.mydomain.com";
    //sess.getProperties()[tr->getInfos().getPropertyPrefix() + "server.port"] = 21;

    // -- you can also set the authentication credentials
    //sess.getProperties()[tr->getInfos().getPropertyPrefix() + "auth.username"] = "username";
    //sess.getProperties()[tr->getInfos().getPropertyPrefix() + "auth.password"] = "password";
    //sess.getProperties()[tr->getInfos().getPropertyPrefix() + "options.need-authentication"] = true;

    // NOTE: another way for providing authentication information is to
    // create a authenticator object and creating you service as following:
    //
    //     class my_auth : public vmime::messaging::authenticator { ... };
    //
    //     my_auth auth = new my_auth;
    //     vmime::messaging::transport* tr = sess.getTransport(auth);
    //
    //

    // Connect to the server
    tr->connect();

    // Expeditor
    vmime::mailbox from("me@somewhere.com");

    // Recipients list
    vmime::mailboxList to;
    to.appendMailbox(new vmime::mailbox("you@somewhere.com"));
    to.appendMailbox(new vmime::mailbox("somebody.else@anywhere.com"));

    vmime::string contents("[...message data: header body...]");
    vmime::utility::inputStreamAdapter is(contents);

    tr->send(from, to, is, contents.length());

    // Note: you could also write this:
    //     vmime::message msg;
    //     ...
    //     tr->send(&msg);

    // Disconnect from server
    tr->disconnect();

    delete (tr);
    //delete (auth);
}
catch (vmime::exception& e)
{
    std::cerr << e;
}
catch (std::exception& e)
{
    std::cerr << "std::exception: " << e.what() << std::endl;
}

Using the store service

The following example illustates the use of the store service to connect to an IMAP server, and retrieve the first message of the INBOX.

try
{
    // If no authenticator is given in argument to getStore(), a default one
    // is used. Its behaviour is to get the user credentials from the
    // session properties "auth.username" and "auth.password".
    vmime::messaging::store* st = sess.getStore();

    // Store protocol configuration
    sess.getProperties()[st->getInfos().getPropertyPrefix() + "auth.username"] = "username";
    sess.getProperties()[st->getInfos().getPropertyPrefix() + "auth.password"] = "password";

    sess.getProperties()[st->getInfos().getPropertyPrefix() + "server.address"] = "imap.mydomain.com";
    //sess.getProperties()[st->getInfos().getPropertyPrefix() + "server.port"] = 143;
    //sess.getProperties()[st->getInfos().getPropertyPrefix() + "server.socket-factory"] = "default";

    // Some options examples if your are using POP3: try to use APOP authentication,
    // and fallback to basic authentication if APOP is not available.
    //sess.getProperties()[st->getInfos().getPropertyPrefix() + "options.apop"] = true;
    //sess.getProperties()[st->getInfos().getPropertyPrefix() + "options.apop.fallback"] = true;

    // Connect to the server
    st->connect();

    // Open the default folder in this store
    vmime::messaging::folder* f = st->getDefaultFolder();

    f->open(vmime::messaging::folder::MODE_READ_ONLY);

    std::cout << f->getMessageCount() << " message(s) in your inbox" << std::endl;

    // Get a pointer to the first message
    vmime::messaging::message* m = f->getMessage(1);

    // To fetch the header
    f->fetchMessage(m, vmime::messaging::folder::FETCH_ENVELOPE |
    	vmime::messaging::folder::FETCH_CONTENT_INFO);

    std::ostringstream header;
    header << m->getHeader().generate();

    // To retrieve the whole message
    std::ostringstream content;
    vmime::utility::outputStreamAdapter os(content);

    m->extract(os);

    // Delete the message object (this does NOT remove it on the server)
    delete (m);

    // Close the folder
    f->close(true);
    delete (f);

    // Disconnect from the server
    st->disconnect();
    delete (st);
}
// ...
catch (vmime::exception& e)
{
    std::cerr << e;
}
catch (std::exception& e)
{
    std::cerr << "std::exception: " << e.what() << std::endl;
}

The following code prints the MIME structure of a message on the standard output:

void printStructure(const vmime::messaging::structure& s, int level = 0)
{
   // Parts are numbered from 1 to total number of parts
   for (int i = 1 ; i <= s.getCount() ; ++i)
   {
      const vmime::messaging::part& part = s[i];

      for (int j = 0 ; j < level * 2 ; ++j)
         std::cout << " ";  // indentation

      // Print information about this part
      std::cout << part.getNumber() << ". "
                << part.getType().generate()
                << " [" << part.getSize() << " byte(s)]"
                << std::endl;

      // Recursively prints the structure
      printStructure(part.getStructure(), level + 1);
   }
}

...

// Fetch message structure
myFolder->fetchMessage(myMessage, vmime::messaging::folder::FETCH_STRUCTURE);

// Print MIME structure on the standard output
printStructure(myMessage->getStructure());

Fetching MIME structure is not supported by all protocols (actually, only IMAP). If you try to fetch structure with a protocol that do not support this, a operation_not_supported exception will be thrown. You will have to extract the full message and use VMime parser tools (vmime::message::parse()) to get the structure.

Let's see how to extract the contents of a specific part in a message (this may not be supported by the protocol, in which case a operation_not_supported exception is thrown). Suppose that the message has the following structure:

  1. multipart/alternative
    1. text/plain
    2. multipart/related
      1. text/html
      2. image/png

This code extracts the contents of the "text/html" part (1.2.1) and streams it to the standard output:

myMessage->extractPart(myMessage->getStructure()[1][2][1], std::cout, NULL);

Note that you can also extract some portion of a specific part (this is called partial fetch, and may not be supported by the protocol: a partial_fetch_not_supported exception will be thrown). For more information, see the prototypes of the functions extract and extractPart in the class message.

There are a lot of things that can be done with messaging stores: retrieve/set message flags, create/delete/rename folders, extract a specific MIME part (IMAP protocol supports this, for example)... You can learn a lot by reading the documentation for messaging::store, messaging::folder and messaging::message abstract classes in the Doxygen-generated documentation.

Setting up a time-out handler

By default, when connecting to messaging servers, VMime waits indefinitely for the server response after each request. You can override this behaviour by setting up a time-out handler.

The abstract class timeoutHandler defines an interface for managing a time-out counter and handling the case where response time is elapsed:

const bool isTimeOut();
void resetTimeOut();
const bool handleTimeOut();

The resetTimeOut() function should reset an internal counter for time-out. The isTimeOut() function should return true if, at the time of the call, the time is elapsed. Then, the handleTimeOut() function will be called to handle the time-out (return true to continue waiting for the response (and reseting the counter), or false to cancel the current operation).

The following code show an example implementation for a simple time-out handler:

class my_timeout_handler : public vmime::messaging::timeoutHandler
{
public:

    // By default, time is out after 60 seconds
    my_timeout_handler(const unsigned int delay = 60)
        : m_delay(delay)
    {
    }

    const bool isTimeOut()
    {
        const unsigned int current = platformDependant::getHandler()->getUnixTime();
        return (current >= m_start + m_delay);
    }

    void resetTimeOut()
    {
        // Reset the time-out counter
        m_start = platformDependant::getHandler()->getUnixTime();
    }

    const bool handleTimeOut()
    {
        std::cout << "The operation timed-out while waiting for a server response.\n";
        std::cout << "Cancel the operation?" << std::cout;

        char answer;
        std::cin >> answer;

        // Return 'true' to continue (the time-out will be reset automatically),
        // or 'false' to cancel the current operation
        return (answer == 'n');
    }

private:

    const unsigned int m_delay;   // time-out delay
    unsigned int m_start;         // start time
};

Now, let's see how to make a service use your time-out handler. Time-out handler objects are instanciated using a factory: the timeoutHandlerFactory. You can specify which time-out handler to use by setting the timeout.handler property on this service:

vmime::messaging::session sess;
sess.getProperties()["store.protocol"] = "pop3";

vmime::messaging::store* st = sess.getStore();

// Store protocol configuration
sess.getProperties()[st->getInfos().getPropertyPrefix() + "auth.username"] = "username";
sess.getProperties()[st->getInfos().getPropertyPrefix() + "auth.password"] = "password";

sess.getProperties()[st->getInfos().getPropertyPrefix() + "server.address"] = "mail.myserver.com";

sess.getProperties()[st->getInfos().getPropertyPrefix() + "timeout.handler"] = "my_pop3handler";

...

Finally, you have to implement the factory for your time-out handler, and to write the code for the getTimeoutHandlerFactory() function in your platform-dependant handler:

class my_timeout_handler_factory
{
    my_timeout_handler* create()
    {
        return new my_timeout_handler(120);
    }
};

class my_handler : public vmime::platformDependant::handler
{
private:

    my_timeout_handler_factory* m_thf;

public:

    my_handler()
        : m_thf(new my_timeout_handler_factory)
    {
    }

    ~my_handler()
    {
        delete (m_thf);
    }

    // ...

    vmime::messaging::timeoutHandlerFactory* getTimeoutHandlerFactory(const string& name) const
    {
        // Here, you could use the "name" parameter to return a different
        // factory depending on, for example, the protocol type. This is
        // the text you set in the "time.handler" property.

        //if (name == "my_pop3handler")
        //    return ...

        return m_thf;
    }
};

Using the progression listener

When you are downloading a message from a store or sending data to a transport service, your application can be notified of how much of the task has been done by using the progressionListener object. The following operations can make use of the progression listener:

All you have to do to tell these functions to use your progression listener is to give a pointer to it in argument (usually the last argument; for more information, see the class documentation).

A progression listener object must inherit from messaging::progressionListener and implement the following functions:

const bool cancel() const;

void start(const int predictedTotal);
void progress(const int current, const int currentTotal);
void stop(const int total);

The cancel() function should return true to cancel the operation. Be conscious that this is implementation-dependant: the messaging protocol may not support this, and in that case, the result is ignored.

start() is called when the operation has started (initialization). The parameter predictedTotal indicate the number of units the operation may last (this can be a number of bytes, a percentage...). This may change during the operation.

The progress() function is called each time an amount of progress units has been completed. The current parameter indicates the total number of units completed, and currentTotal the new total (this may be the same as in start()).

Finally, the stop() function is called when the operation is finished. The total parameter indicates how many units the operation lasted.

The following code is an example for implementing a progression listener:

class my_progress_listener : public vmime::messaging::progressionListener
{
protected:

    virtual ~progressionListener() { }

public:

    // Return true to cancel the operation (this is implementation-dependant:
    // messaging protocol may not support this)
    const bool cancel()
    {
        return false;
    }

    // Progression notification
    void start(const int predictedTotal)
    {
        std::cout << "Operation started..." << std::endl;
        std::cout << "0%";
        std::cout.flush();
    }

    void progress(const int current, const int currentTotal)
    {
        const int percent = (100 / currentTotal) * current;

        std::cout << "\r" << percent << "%";
        std::cout.flush();
    }

    void stop(const int total)
    {
        std::cout << "\n";
        std::cout << "Operation finished!" << std::endl;
    }
};

And the code to use it:

...
vmime::messaging::message* msg = folder->getMessage(1);

my_progress_listener progress;

std::ostringstream content;
vmime::utility::outputStreamAdapter os(content);

msg->extract(os, &progress);
...

Miscellaneous

Decoding text from vmime::text

vmime::text can contain text in multiple charsets (see The text type). You may want to extract the whole text into one unique charset (for example, with an Unicode encoding not to loose any information). This is the goal of the getConvertedText() method.

For example, the following code:

   vmime::text txt;
   txt.appendWord(new vmime::word("D\x82codage de texte", vmime::charset(vmime::charsets::ISO_8859_1)));
   txt.appendWord(new vmime::word(" avec VMime", vmime::charset(vmime::charsets::US_ASCII)));

   const vmime::string dec = txt.getConvertedText(vmime::charsets::UTF_8);

   std::cout << dec << std::endl;

will output the string "Décodage de texte avec VMime" in the UTF-8 encoding.

Using encoders/decoders

To encode or decode data, you have to get a new instance to an encoder object. Encoder instances are created by encoderFactory:

   vmime::encoder* enc = vmime::encoderFactory::getInstance()->create("base64");

Then, you can call either the encode() or decode() method on the object. Please note that encoding/decoding is done using streams.

   const utility::stream::size_type encode(utility::inputStream& in, utility::outputStream& out);
   const utility::stream::size_type decode(utility::inputStream& in, utility::outputStream& out);

You can encode or decode a string using a stream adapter:

   vmime::string inString = "Data to decode";
   vmime::utility::inputStreamStringAdapter in(inString);

   vmime::string outString;
   vmime::utility::outputStreamStringAdapter out(outString);

   enc->decode(in, out);

   std::cout << "Decoded data = " << outString << std::endl;

Extending VMime

Custom header fields

By default, unknown (non-standard) header fields are of type defaultField (the contents is then interpreted as simple text). You can change this behaviour and tell VMime what type of field (class) to use for a particular name. This is called custom header fields.

For example, to tell VMime the field with name "X-MyField:" should contain a date (the same format as the field 'Date:'), and that we want to access it like a dateField object, you can write:

// Register the name at the header field factory
vmime::headerFieldFactory::getInstance()->registerName <vmime::dateField>("X-MyField");

// Now, the fields with name "X-MyField:" should be accessed as objects of
// type "dateField" (and no more "defaultField")...
dynamic_cast <vmime::dateField&>(*myMessage->getHeader()->getField("X-MyField"))
        = vmime::datetime("21 May 2003 00:03:00 +0200");

NOTE: standard fields (eg. Date, From, To, ...) can not be reinterpreted (re-registering them at the header field factory has no effect). To know the complete list of the fields, please see the related constants vmime::fields::XXX in file constants.hpp.

Custom encoders

By default, the following encodings are available: base64, quoted-printable, uuencode, 7bit, 8bit, binary. You can define your own encodings and tell VMime to use them when needed.

To do this, you have to create a new class that implements the vmime::encoder interface, and register it at the encoder factory (object encoderFactory). The following example illustrates the use of custom encodings:

class myEncoder : public vmime::encoder
{
    const utility::stream::size_type encode
        (utility::inputStream& in, utility::outputStream& out)
    {
        in.reset();  // may not work...

        // No encoding performed (TODO: write your own encoding algorithm)
        return (utility::bufferedStreamCopy(in, out));
    }

    const utility::stream::size_type decode
        (utility::inputStream& in, utility::outputStream& out)
    {
        in.reset();  // may not work...

        // No decoding performed (TODO: write your own decoding algorithm)
        return (utility::bufferedStreamCopy(in, out));
    }
};

...

// Register the new encoding
vmime::encoderFactory::getInstance()->registerName <myEncoder>("x-my-encoding");

// From now, VMime will use your encoder "myEncoder" when it will need to encode
// or decode some contents with "Content-Transfer-Encoding:" set to "x-my-encoding".