src/messaging/win32wce/qmailmessage.cpp
changeset 0 876b1a06bc25
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/messaging/win32wce/qmailmessage.cpp	Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,6927 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmailmessage_p.h"
+#include "qmailaddress.h"
+#include "qmailcodec.h"
+#include "qmaillog.h"
+#include "qmailnamespace.h"
+#include "qmailtimestamp.h"
+#include "longstring_p.h"
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+#include "qmailaccount.h"
+#include "qmailfolder.h"
+#include "qmailstore.h"
+#endif
+
+#include <qcryptographichash.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qregexp.h>
+#include <qtextstream.h>
+#include <qtextcodec.h>
+#include <QTextCodec>
+#include <QtDebug>
+
+#include <stdlib.h>
+#include <limits.h>
+#if defined(Q_OS_WIN) && defined(_WIN32_WCE)
+#include <cctype>
+#else
+#include <ctype.h>
+#endif
+
+static const QByteArray internalPrefix()
+{
+    static const QByteArray prefix("X-qtopiamail-internal-");
+    return prefix;
+}
+
+template<typename CharType>
+inline char toPlainChar(CharType value) { return value; }
+
+template<>
+inline char toPlainChar<QChar>(QChar value) { return static_cast<char>(value.unicode() & 0x7f); }
+
+template<typename CharType>
+inline bool asciiRepresentable(const CharType& value) { return ((value <= 127) && (value >= 0)); }
+
+template<>
+inline bool asciiRepresentable<unsigned char>(const unsigned char& value) { return (value <= 127); }
+
+template<>
+inline bool asciiRepresentable<signed char>(const signed char& value) { return (value >= 0); }
+
+// The correct test for char depends on whether the platform defines char as signed or unsigned
+// Default to signed char:
+template<bool SignedChar>
+inline bool asciiRepresentableChar(const char& value) { return asciiRepresentable(static_cast<signed char>(value)); }
+
+template<>
+inline bool asciiRepresentableChar<false>(const char& value) { return asciiRepresentable(static_cast<unsigned char>(value)); }
+
+template<>
+inline bool asciiRepresentable<char>(const char& value) { return asciiRepresentableChar<(SCHAR_MIN < CHAR_MIN)>(value); }
+
+template<typename StringType>
+QByteArray to7BitAscii(const StringType& src)
+{
+    QByteArray result;
+    result.reserve(src.length());
+
+    typename StringType::const_iterator it = src.begin();
+    for (const typename StringType::const_iterator end = it + src.length(); it != end; ++it)
+        if (asciiRepresentable(*it))
+            result.append(toPlainChar(*it));
+
+    return result;
+}
+
+
+// Parsing functions
+static int insensitiveIndexOf(const QByteArray& content, const QByteArray& container, int from = 0)
+{
+    const char* const matchBegin = content.constData();
+    const char* const matchEnd = matchBegin + content.length();
+
+    const char* const begin = container.constData();
+    const char* const end = begin + container.length() - (content.length() - 1);
+
+    const char* it = begin + from;
+    while (it < end)
+    {
+        if (toupper(*it++) == toupper(*matchBegin))
+        {
+            const char* restart = it;
+
+            // See if the remainder matches
+            const char* searchIt = it;
+            const char* matchIt = matchBegin + 1;
+
+            do 
+            {
+                if (matchIt == matchEnd)
+                    return ((it - 1) - begin);
+
+                // We may find the next place to search in our scan
+                if ((restart == it) && (*searchIt == *(it - 1)))
+                    restart = searchIt;
+            }
+            while (toupper(*searchIt++) == toupper(*matchIt++));
+
+            // No match
+            it = restart;
+        }
+    }
+
+    return -1;
+}
+
+static bool insensitiveEqual(const QByteArray& lhs, const QByteArray& rhs)
+{
+    if (lhs.isNull() || rhs.isNull())
+        return (lhs.isNull() && rhs.isNull());
+
+    if (lhs.length() != rhs.length())
+        return false;
+
+    return insensitiveIndexOf(lhs, rhs) == 0;
+}
+
+static QByteArray charsetForInput(const QString& input)
+{
+    // See if this input needs encoding
+    bool latin1 = false;
+
+    const QChar* it = input.constData();
+    const QChar* const end = it + input.length();
+    for ( ; it != end; ++it)
+    {
+        if ((*it).unicode() > 0xff)
+        {
+            // Multi-byte characters included - we need to use UTF-8
+            return QByteArray("UTF-8");
+        }
+        else if (!latin1 && ((*it).unicode() > 0x7f))
+        {
+            // We need encoding from latin-1
+            latin1 = true;
+        }
+    }
+
+    return (latin1? QByteArray("ISO-8859-1") : QByteArray());
+}
+
+static QTextCodec* codecForName(const QByteArray& charset, bool translateAscii = true)
+{
+    QByteArray encoding(charset.toLower());
+
+    if (!encoding.isEmpty())
+    {
+        int index;
+
+        if (translateAscii && encoding.contains("ascii")) 
+        {
+            // We'll assume the text is plain ASCII, to be extracted to Latin-1
+            encoding = "ISO-8859-1";
+        }
+        else if ((index = encoding.indexOf('*')) != -1)
+        {
+            // This charset specification includes a trailing language specifier
+            encoding = encoding.left(index);
+        }
+
+        return QTextCodec::codecForName(encoding);
+    }
+
+    return 0;
+}
+
+static QByteArray fromUnicode(const QString& input, const QByteArray& charset)
+{
+    if (!charset.isEmpty() && (insensitiveIndexOf("ascii", charset) == -1))
+    {
+        // See if we can convert using the nominated charset
+        if (QTextCodec* textCodec = codecForName(charset))
+            return textCodec->fromUnicode(input);
+
+        qWarning() << "fromUnicode: unable to find codec for charset:" << charset;
+    }
+
+    return to7BitAscii(input.toLatin1());
+}
+
+static QString toUnicode(const QByteArray& input, const QByteArray& charset)
+{
+    if (!charset.isEmpty() && (insensitiveIndexOf("ascii", charset) == -1))
+    {
+        // See if we can convert using the nominated charset
+        if (QTextCodec* textCodec = codecForName(charset))
+            return textCodec->toUnicode(input);
+
+        qWarning() << "toUnicode: unable to find codec for charset:" << charset;
+    }
+
+    return to7BitAscii(QString::fromLatin1(input.constData(), input.length()));
+}
+
+static QMailMessageBody::TransferEncoding encodingForName(const QByteArray& name)
+{
+    QByteArray ciName = name.toLower();
+
+    if (ciName == "7bit")
+        return QMailMessageBody::SevenBit;
+    if (ciName == "8bit")
+        return QMailMessageBody::EightBit;
+    if (ciName == "base64")
+        return QMailMessageBody::Base64;
+    if (ciName == "quoted-printable")
+        return QMailMessageBody::QuotedPrintable;
+    if (ciName == "binary")
+        return QMailMessageBody::Binary;
+
+    return QMailMessageBody::NoEncoding;
+}
+
+static const char* nameForEncoding(QMailMessageBody::TransferEncoding te)
+{
+    switch( te ) 
+    {
+        case QMailMessageBody::SevenBit:
+            return "7bit";
+        case QMailMessageBody::EightBit:
+            return "8bit";
+        case QMailMessageBody::QuotedPrintable:
+            return "quoted-printable";
+        case QMailMessageBody::Base64:
+            return "base64";
+        case QMailMessageBody::Binary:
+            return "binary";
+        case QMailMessageBody::NoEncoding:
+            break;
+    }
+
+    return 0;
+}
+
+static QMailCodec* codecForEncoding(QMailMessageBody::TransferEncoding te, bool textualData)
+{
+    switch( te ) 
+    {
+        case QMailMessageBody::NoEncoding:
+        case QMailMessageBody::Binary:
+            return new QMailPassThroughCodec();
+
+        case QMailMessageBody::SevenBit:
+        case QMailMessageBody::EightBit:
+            return (textualData ? static_cast<QMailCodec*>(new QMailLineEndingCodec()) : new QMailPassThroughCodec());
+
+        case QMailMessageBody::QuotedPrintable:
+            return new QMailQuotedPrintableCodec(textualData ? QMailQuotedPrintableCodec::Text : QMailQuotedPrintableCodec::Binary, QMailQuotedPrintableCodec::Rfc2045);
+
+        case QMailMessageBody::Base64:
+            return new QMailBase64Codec(textualData ? QMailBase64Codec::Text : QMailBase64Codec::Binary);
+    }
+
+    return 0;
+}
+
+static QMailCodec* codecForEncoding(QMailMessageBody::TransferEncoding te, const QMailMessageContentType& content)
+{
+    return codecForEncoding(te, insensitiveEqual(content.type(), "text"));
+}
+
+//  Needs an encoded word of the form =?charset?q?word?=
+static QString decodeWord(const QByteArray& encodedWord)
+{
+    QString result;
+    int index[4];
+
+    // Find the parts of the input
+    index[0] = encodedWord.indexOf("=?");
+    if (index[0] != -1)
+    {
+        index[1] = encodedWord.indexOf('?', index[0] + 2);
+        if (index[1] != -1)
+        {
+            index[2] = encodedWord.indexOf('?', index[1] + 1);
+            index[3] = encodedWord.lastIndexOf("?=");
+            if ((index[2] != -1) && (index[3] > index[2]))
+            {
+                QByteArray charset = QMail::unquoteString(encodedWord.mid(index[0] + 2, (index[1] - index[0] - 2)));
+                QByteArray encoding = encodedWord.mid(index[1] + 1, (index[2] - index[1] - 1)).toUpper();
+                QByteArray encoded = encodedWord.mid(index[2] + 1, (index[3] - index[2] - 1));
+
+                if (encoding == "Q")
+                {
+                    QMailQuotedPrintableCodec codec(QMailQuotedPrintableCodec::Text, QMailQuotedPrintableCodec::Rfc2047);
+                    result = codec.decode(encoded, charset);
+                }
+                else if (encoding == "B")
+                {
+                    QMailBase64Codec codec(QMailBase64Codec::Binary);
+                    result = codec.decode(encoded, charset);
+                }
+            }
+        }
+    }
+
+    if (result.isEmpty())
+        result = encodedWord;
+
+    return result;
+}
+
+static QByteArray generateEncodedWord(const QByteArray& codec, char encoding, const QByteArray& text)
+{
+    QByteArray result("=?");
+    result.append(codec);
+    result.append('?');
+    result.append(encoding);
+    result.append('?');
+    result.append(text);
+    result.append("?=");
+    return result;
+}
+
+static QByteArray generateEncodedWord(const QByteArray& codec, char encoding, const QList<QByteArray>& list)
+{
+    QByteArray result;
+
+    foreach (const QByteArray& item, list)
+    {
+        if (!result.isEmpty())
+            result.append(' ');
+
+        result.append(generateEncodedWord(codec, encoding, item));
+    }
+
+    return result;
+}
+
+static QList<QByteArray> split(const QByteArray& input, const QByteArray& separator)
+{
+    QList<QByteArray> result;
+
+    int index = -1;
+    int lastIndex = -1;
+    do
+    {
+        lastIndex = index;
+        index = input.indexOf(separator, lastIndex + 1);
+
+        int offset = (lastIndex == -1 ? 0 : lastIndex + separator.length());
+        int length = (index == -1 ? -1 : index - offset);
+        result.append(input.mid(offset, length));
+    } while (index != -1);
+
+    return result;
+}
+
+static QByteArray encodeWord(const QString &text, const QByteArray& cs, bool* encoded)
+{
+    // Do we need to encode this input?
+    QByteArray charset(cs);
+    if (charset.isEmpty())
+        charset = charsetForInput(text);
+
+    if (encoded)
+        *encoded = true;
+
+    // We can't allow more than 75 chars per encoded-word, including the boiler plate...
+    int maximumEncoded = 75 - 7 - charset.length();
+
+    // If this is an encodedWord, we need to include any whitespace that we don't want to lose
+    if (insensitiveIndexOf("utf-8", charset) == 0)
+    {
+        QMailBase64Codec codec(QMailBase64Codec::Binary, maximumEncoded);
+        QByteArray encoded = codec.encode(text, charset);
+        return generateEncodedWord(charset, 'B', split(encoded, QMailMessage::CRLF));
+    }
+    else if (insensitiveIndexOf("iso-8859-", charset) == 0)
+    {
+        QMailQuotedPrintableCodec codec(QMailQuotedPrintableCodec::Text, QMailQuotedPrintableCodec::Rfc2047, maximumEncoded);
+        QByteArray encoded = codec.encode(text, charset);
+        return generateEncodedWord(charset, 'Q', split(encoded, "=\n"));
+    }
+
+    if (encoded)
+        *encoded = false;
+
+    return to7BitAscii(text);
+}
+
+static QString decodeWordSequence(const QByteArray& str)
+{
+    static const QRegExp whitespace("^\\s+$");
+
+    QString out;
+
+    // Any idea why this isn't matching?
+    //QRegExp encodedWord("\\b=\\?\\S+\\?\\S+\\?\\S*\\?=\\b");
+    QRegExp encodedWord("=\\?\\S+\\?\\S+\\?\\S*\\?=");
+
+    int pos = 0;
+    int lastPos = 0;
+    int length = str.length();
+
+    while (pos != -1) {
+        pos = encodedWord.indexIn(str, pos);
+        if (pos != -1) {
+            int endPos = pos + encodedWord.matchedLength();
+
+            if ( ((pos == 0) || (::isspace(str[pos - 1]))) &&
+                 ((endPos == length) || (::isspace(str[endPos]))) ) {
+
+                QString preceding(str.mid(lastPos, (pos - lastPos)));
+                QString decoded = decodeWord(str.mid(pos, (endPos - pos)));
+
+                // If there is only whitespace between two encoded words, it should not be included
+                if (!whitespace.exactMatch(preceding))
+                    out.append(preceding);
+
+                out.append(decoded);
+
+                pos = endPos;
+                lastPos = pos;
+            }
+            else
+                pos = endPos;
+        }
+    }
+
+    // Copy anything left
+    out.append(str.mid(lastPos));
+
+    return out;
+}
+
+enum EncodingTokenType
+{
+    Whitespace,
+    Word,
+    Quote
+};
+
+typedef QPair<const QChar*, int> TokenRange;
+typedef QPair<EncodingTokenType, TokenRange> Token;
+
+static Token makeToken(EncodingTokenType type, const QChar* begin, const QChar* end, bool escaped)
+{
+    return qMakePair(type, qMakePair(begin, (int)(end - begin) - (escaped ? 1 : 0)));
+}
+
+static QList<Token> tokenSequence(const QString& input)
+{
+    QList<Token> result;
+
+    bool escaped = false;
+
+    const QChar* it = input.constData();
+    const QChar* const end = it + input.length();
+    if (it != end) 
+    {
+        const QChar* token = it;
+        EncodingTokenType state = ((*it) == '"' ? Quote : ((*it).isSpace() ? Whitespace : Word)); 
+
+        for (++it; it != end; ++it) 
+        {
+            if (!escaped && (*it == '\\')) 
+            {
+                escaped = true;
+                continue;
+            }
+
+            if (state == Quote)
+            {
+                // This quotation mark is a token by itself
+                result.append(makeToken(state, token, it, escaped));
+
+                state = ((*it) == '"' && !escaped ? Quote : ((*it).isSpace() ? Whitespace : Word)); 
+                token = it;
+            }
+            else if (state == Whitespace)
+            {
+                if (!(*it).isSpace())
+                {
+                    // We have passed the end of this whitespace-sequence
+                    result.append(makeToken(state, token, it, escaped));
+
+                    state = ((*it) == '"' && !escaped ? Quote : Word);
+                    token = it;
+                }
+            }
+            else
+            {
+                if ((*it).isSpace() || ((*it) == '"' && !escaped))
+                {
+                    // We have passed the end of this word
+                    result.append(makeToken(state, token, it, escaped));
+
+                    state = ((*it).isSpace() ? Whitespace : Quote);
+                    token = it;
+                }
+            }
+
+            escaped = false;
+        }
+
+        result.append(makeToken(state, token, it, false));
+    }
+
+    return result;
+}
+
+static QByteArray encodeWordSequence(const QString& str, const QByteArray& charset)
+{
+    QByteArray result;
+
+    bool quoted = false;
+    bool tokenEncoded = false;
+    QString quotedText;
+    QString heldWhitespace;
+
+    foreach (const Token& token, tokenSequence(str))
+    {
+        QString chars = QString::fromRawData(token.second.first, token.second.second);
+
+        // See if we're processing some quoted words
+        if (quoted)
+        {
+            if (token.first == Quote)
+            {
+                // We have reached the end of a quote sequence
+                quotedText.append(chars);
+
+                bool lastEncoded = tokenEncoded;
+
+                QByteArray output = encodeWord(heldWhitespace + quotedText, charset, &tokenEncoded);
+
+                quotedText = QString();
+                quoted = false;
+                heldWhitespace = QString();
+
+                if (lastEncoded && tokenEncoded)
+                    result.append(' ');
+                result.append(output);
+            }
+            else
+            {
+                quotedText.append(chars);
+            }
+        }
+        else
+        {
+            if (token.first == Quote)
+            {
+                // This token begins a quoted sequence
+                quotedText = chars;
+                quoted = true;
+            }
+            else
+            {
+                if (token.first == Word)
+                {
+                    bool lastEncoded = tokenEncoded;
+
+                    // See if this token needs encoding
+                    QByteArray output = encodeWord(heldWhitespace + chars, charset, &tokenEncoded);
+                    heldWhitespace = QString();
+
+                    if (lastEncoded && tokenEncoded)
+                        result.append(' ');
+                    result.append(output);
+                }
+                else // whitespace
+                {
+                    // If the last token was an encoded-word, we may need to include this
+                    // whitespace into the next token
+                    if (tokenEncoded)
+                        heldWhitespace.append(chars);
+                    else
+                        result.append(chars.toAscii());
+                }
+            }
+        }
+    }
+
+    return result;
+}
+
+static int hexValue(char value)
+{
+    // Although RFC 2231 requires capitals, we may as well accept miniscules too
+    if (value >= 'a')
+        return (((value - 'a') + 10) & 0x0f);
+    if (value >= 'A')
+        return (((value - 'A') + 10) & 0x0f);
+
+    return ((value - '0') & 0x0f);
+}
+
+static int hexValue(const char* it)
+{
+    return ((hexValue(*it) << 4) | hexValue(*(it + 1)));
+}
+
+static QString decodeParameterText(const QByteArray& text, const QByteArray& charset)
+{
+    QByteArray decoded;
+    decoded.reserve(text.length());
+
+    // Decode any encoded bytes in the data
+    const char* it = text.constData();
+    for (const char* const end = it + text.length(); it != end; ++it)
+    {
+        if (*it == '%')
+        {
+            if ((end - it) > 2)
+                decoded.append(hexValue(it + 1));
+
+            it += 2;
+        }
+        else
+            decoded.append(*it);
+    }
+
+    // Decoded contains a bytestream - decode to unicode text if possible
+    return toUnicode(decoded, charset);
+}
+
+//  Needs an encoded parameter of the form charset'language'text
+static QString decodeParameter(const QByteArray& encodedParameter)
+{
+    QRegExp parameterFormat("([^']*)'(?:[^']*)'(.*)");
+    if (parameterFormat.exactMatch(encodedParameter))
+        return decodeParameterText(parameterFormat.cap(2).toLatin1(), parameterFormat.cap(1).toLatin1());
+
+    // Treat the whole thing as input, and deafult the charset to ascii
+    // This is not required by the RFC, since the input is illegal.  But, it 
+    // seems ok since the parameter name has already indicated that the text 
+    // should be encoded...
+    return decodeParameterText(encodedParameter, "us-ascii");
+}
+
+static char hexRepresentation(int value)
+{
+    value &= 0x0f;
+
+    if (value < 10)
+        return ('0' + value);
+    return ('A' + (value - 10));
+}
+
+static QByteArray generateEncodedParameter(const QByteArray& charset, const QByteArray& language, const QByteArray& text)
+{
+    QByteArray result(charset);
+    QByteArray lang(language);
+
+    // If the charset contains a language part, extract it
+    int index = result.indexOf('*');
+    if (index != -1)
+    {
+        // If no language is specfied, use the extracted part
+        if (lang.isEmpty())
+            lang = result.mid(index + 1);
+
+        result = result.left(index);
+    }
+
+    result.append('\'');
+    result.append(lang);
+    result.append('\'');
+    
+    // Have a guess at how long the result will be
+    result.reserve(result.length() + (2 * text.length()));
+
+    // We could encode the exact set of permissible characters here, but they're basically the alphanumerics
+    const char* it = text.constData();
+    const char* const end = it + text.length();
+    for ( ; it != end; ++it) {
+        if (::isalnum(static_cast<unsigned char>(*it))) {
+            result.append(*it);
+        } else {
+            // Encode to hex
+            int value = (*it);
+            result.append('%').append(hexRepresentation(value >> 4)).append(hexRepresentation(value));
+        }
+    }
+
+    return result;
+}
+
+static QByteArray encodeParameter(const QString &text, const QByteArray& charset, const QByteArray& language)
+{
+    QByteArray encoding(charset);
+    if (encoding.isEmpty())
+        encoding = charsetForInput(text);
+
+    return generateEncodedParameter(encoding, language, fromUnicode(text, encoding));
+}
+
+static QByteArray removeComments(const QByteArray& input, int (*classifier)(int), bool acceptedResult = true)
+{
+    QByteArray result;
+
+    int commentDepth = 0;
+    bool quoted = false;
+    bool escaped = false;
+
+    const char* it = input.constData();
+    const char* const end = it + input.length();
+    for ( ; it != end; ++it ) {
+        if ( !escaped && ( *it == '\\' ) ) {
+            escaped = true;
+            continue;
+        }
+
+        if ( *it == '(' && !escaped && !quoted ) {
+            commentDepth += 1;
+        }
+        else if ( *it == ')' && !escaped && !quoted && ( commentDepth > 0 ) ) {
+            commentDepth -= 1;
+        }
+        else {
+            bool quoteProcessed = false;
+            if ( !quoted && *it == '"' && !escaped ) {
+                quoted = true;
+                quoteProcessed = true;
+            }
+
+            if ( commentDepth == 0 ) {
+                if ( quoted || (bool((*classifier)(*it)) == acceptedResult) )
+                    result.append( *it );
+            }
+
+            if ( quoted && !quoteProcessed && *it == '"' && !escaped ) {
+                quoted = false;
+            }
+        }
+
+        escaped = false;
+    }
+
+    return result;
+}
+
+
+// Necessary when writing to QDataStream, because the string/char literal is encoded
+// in various pre-processed ways...
+
+struct DataString
+{
+    DataString(char datum) : _datum(datum), _data(0), _length(0) {};
+    DataString(const char* data) : _datum('\0'), _data(data), _length(strlen(_data)) {};
+    DataString(const QByteArray& array) : _datum('\0'), _data(array.constData()), _length(array.length()) {};
+
+    inline QDataStream& toDataStream(QDataStream& out) const
+    {
+        if (_data)
+            out.writeRawData(_data, _length);
+        else if (_datum == '\n')
+            // Ensure that line-feeds are always CRLF sequences
+            out.writeRawData(QMailMessage::CRLF, 2);
+        else if (_datum != '\0')
+            out.writeRawData(&_datum, 1);
+
+        return out;
+    }
+
+private:
+    char _datum;
+    const char* _data;
+    int _length;
+};
+
+QDataStream& operator<<(QDataStream& out, const DataString& dataString)
+{
+    return dataString.toDataStream(out);
+}
+
+
+/* QMailMessageHeaderField */
+
+QMailMessageHeaderFieldPrivate::QMailMessageHeaderFieldPrivate()
+    : QPrivateImplementationBase(this),
+      _structured(true)
+{
+}
+
+QMailMessageHeaderFieldPrivate::QMailMessageHeaderFieldPrivate(const QByteArray& text, bool structured)
+    : QPrivateImplementationBase(this)
+{
+    parse(text, structured);
+}
+
+QMailMessageHeaderFieldPrivate::QMailMessageHeaderFieldPrivate(const QByteArray& id, const QByteArray& text, bool structured)
+    : QPrivateImplementationBase(this)
+{
+    _id = id;
+    parse(text, structured);
+}
+
+static bool validExtension(const QByteArray& trailer, int* number = 0, bool* encoded = 0)
+{
+    // Extensions according to RFC 2231:
+    QRegExp extensionFormat("(?:\\*(\\d+))?(\\*?)");
+    if (extensionFormat.exactMatch(trailer))
+    {
+        if (number)
+            *number = extensionFormat.cap(1).toInt();
+        if (encoded)
+            *encoded = !extensionFormat.cap(2).isEmpty();
+
+        return true;
+    }
+    else
+        return false;
+}
+
+static bool matchingParameter(const QByteArray& name, const QByteArray& other, bool* encoded = 0)
+{
+    QByteArray match(name.trimmed());
+
+    int index = insensitiveIndexOf(match, other);
+    if (index == -1)
+        return false;
+
+    if (index > 0)
+    {
+        // Ensure that every preceding character is whitespace
+        QByteArray leader(other.left(index).trimmed());
+        if (!leader.isEmpty())
+            return false;
+    }
+
+    int lastIndex = index + match.length() - 1;
+    index = other.indexOf('=', lastIndex);
+    if (index == -1)
+        index = other.length();
+
+    // Ensure that there is only whitespace between the matched name and the end of the name
+    if ((index - lastIndex) > 1)
+    {
+        QByteArray trailer(other.mid(lastIndex + 1, (index - lastIndex)).trimmed());
+        if (!trailer.isEmpty())
+            return validExtension(trailer, 0, encoded);
+    }
+
+    return true;
+}
+
+void QMailMessageHeaderFieldPrivate::addParameter(const QByteArray& name, const QByteArray& value)
+{
+    _parameters.append(qMakePair(name, QMail::unquoteString(value)));
+}
+
+void QMailMessageHeaderFieldPrivate::parse(const QByteArray& text, bool structured)
+{
+    _structured = structured;
+
+    // Parse text into main and params
+    const char* const begin = text.constData();
+    const char* const end = begin + text.length();
+
+    bool malformed = false;
+
+    const char* token = begin;
+    const char* firstToken = begin;
+    const char* it = begin;
+    const char* separator = 0;
+    for (bool quoted = false; it != end; ++it)
+    {
+        if (*it == '"') {
+            quoted = !quoted;
+        }
+        else if (*it == ':' && !quoted && token == begin) {
+            // This is the end of the field id
+            if (_id.isEmpty()) {
+                _id = QByteArray(token, (it - token)).trimmed();
+                token = (it + 1);
+            }
+            else if (_structured) {
+                // If this is a structured header, there can be only one colon
+                token = (it + 1);
+            }
+            firstToken = token;
+        }
+        else if (*it == '=' && !quoted && structured) {
+            if (separator == 0) {
+                // This is a parameter separator
+                separator = it;
+            }
+            else  {
+                // It would be nice to identify extra '=' chars, but it's too hard
+                // to separate them from encoded-word formations...
+                //malformed = true;
+            }
+        }
+        else if (*it == ';' && !quoted && structured) {
+            // This is the end of a token
+            if (_content.isEmpty()) {
+                _content = QByteArray(token, (it - token)).trimmed();
+            }
+            else if ((separator > token) && ((separator + 1) < it)) {
+                QByteArray name = QByteArray(token, (separator - token)).trimmed();
+                QByteArray value = QByteArray(separator + 1, (it - separator - 1)).trimmed();
+
+                if (!name.isEmpty() && !value.isEmpty())
+                    addParameter(name, value);
+            }
+            else {
+                malformed = true;
+            }
+
+            token = (it + 1);
+            separator = 0;
+        }
+    }
+
+    if (token != end) {
+        if (_id.isEmpty()) {
+            _id = QByteArray(token, (end - token)).trimmed();
+        }
+        else if (_content.isEmpty()) {
+            _content = QByteArray(token, (end - token)).trimmed();
+        }
+        else if ((separator > token) && ((separator + 1) < end) && !malformed) {
+            QByteArray name = QByteArray(token, (separator - token)).trimmed();
+            QByteArray value = QByteArray(separator + 1, (end - separator - 1)).trimmed();
+
+            if (!name.isEmpty() && !value.isEmpty())
+                addParameter(name, value);
+        }
+        else if (_structured) {
+            malformed = true;
+        }
+    }
+}
+
+bool QMailMessageHeaderFieldPrivate::operator== (const QMailMessageHeaderFieldPrivate& other) const
+{
+    if (!insensitiveEqual(_id, other._id))
+        return false;
+    
+    if (_content != other._content)
+        return false;
+
+    if (_parameters.count() != other._parameters.count())
+        return false;
+
+    QList<QMailMessageHeaderField::ParameterType>::const_iterator it = _parameters.begin(), end = _parameters.end();
+    QList<QMailMessageHeaderField::ParameterType>::const_iterator oit = other._parameters.begin();
+    for ( ; it != end; ++it, ++oit)
+        if (((*it).first != (*oit).first) || ((*it).second != (*oit).second))
+            return false;
+
+    return true;
+}
+
+bool QMailMessageHeaderFieldPrivate::isNull() const
+{
+    return (_id.isNull() && _content.isNull());
+}
+
+QByteArray QMailMessageHeaderFieldPrivate::id() const
+{
+    return _id;
+}
+
+void QMailMessageHeaderFieldPrivate::setId(const QByteArray& text)
+{
+    _id = text;
+}
+
+QByteArray QMailMessageHeaderFieldPrivate::content() const
+{
+    return _content;
+}
+
+void QMailMessageHeaderFieldPrivate::setContent(const QByteArray& text)
+{
+    _content = text;
+}
+
+QByteArray QMailMessageHeaderFieldPrivate::parameter(const QByteArray& name) const
+{
+    // Coalesce folded parameters into a single return value
+    QByteArray result;
+
+    QByteArray param = name.trimmed();
+    foreach (const QMailMessageContentType::ParameterType& parameter, _parameters) {
+        if (matchingParameter(param, parameter.first))
+            result.append(parameter.second);
+    }
+
+    return result;
+}
+
+void QMailMessageHeaderFieldPrivate::setParameter(const QByteArray& name, const QByteArray& value)
+{
+    if (!_structured)
+        return;
+
+    QByteArray param = name.trimmed();
+
+    bool encoded = false;
+    int index = param.indexOf('*');
+    if (index != -1) {
+        encoded = true;
+        param = param.left(index);
+    }
+
+    // Find all existing parts of this parameter, if present
+    QList<QList<QMailMessageHeaderField::ParameterType>::iterator> matches;
+    QList<QMailMessageHeaderField::ParameterType>::iterator it = _parameters.begin(), end = _parameters.end();
+    for ( ; it != end; ++it) {
+        if (matchingParameter(param, (*it).first))
+            matches.prepend(it);
+    }
+
+    while (matches.count() > 1)
+        _parameters.erase(matches.takeFirst());
+    if (matches.count() == 1)
+        it = matches.takeFirst();
+    
+    // If the value is too long to fit on one line, break it into manageable pieces
+    const int maxInputLength = 78 - 9 - param.length();
+
+    if (value.length() > maxInputLength) {
+        // We have multiple pieces to insert
+        QList<QByteArray> pieces;
+        QByteArray input(value);
+        do
+        {
+            pieces.append(input.left(maxInputLength));
+            input = input.mid(maxInputLength);
+        } while (input.length());
+
+        if (it == end) {
+            // Append each piece at the end
+            int n = 0;
+            while (pieces.count() > 0) {
+                QByteArray id(param);
+                id.append('*').append(QByteArray::number(n));
+                if (encoded && (n == 0))
+                    id.append('*');
+
+                _parameters.append(qMakePair(id, pieces.takeFirst()));
+                ++n;
+            }
+        }
+        else {
+            // Overwrite the remaining instance of the parameter, and place any 
+            // following pieces immediately after
+            int n = pieces.count() - 1;
+            int initial = n;
+
+            while (pieces.count() > 0) {
+                QByteArray id(param);
+                id.append('*').append(QByteArray::number(n));
+                if (encoded && (n == 0))
+                    id.append('*');
+
+                QMailMessageHeaderField::ParameterType parameter = qMakePair(id, pieces.takeLast());
+                if (n == initial) {
+                    // Put the last piece into the existing position
+                    (*it) = parameter;
+                }
+                else {
+                    // Insert before the previous piece, and record the new iterator
+                    it = _parameters.insert(it, parameter);
+                }
+
+                --n;
+            }
+        }
+    }
+    else {
+        // Just one part to insert
+        QByteArray id(param);
+        if (encoded)
+            id.append('*');
+        QMailMessageHeaderField::ParameterType parameter = qMakePair(id, value);
+
+        if (it == end) {
+            _parameters.append(parameter);
+        }
+        else {
+            (*it) = parameter;
+        }
+    }
+}
+
+bool QMailMessageHeaderFieldPrivate::isParameterEncoded(const QByteArray& name) const
+{
+    QByteArray param = name.trimmed();
+
+    bool encoded = false;
+    foreach (const QMailMessageContentType::ParameterType& parameter, _parameters)
+        if (matchingParameter(param, parameter.first, &encoded))
+            return encoded;
+
+    return false;
+}
+
+void QMailMessageHeaderFieldPrivate::setParameterEncoded(const QByteArray& name)
+{
+    QByteArray param = name.trimmed();
+
+    QList<QMailMessageHeaderField::ParameterType>::iterator it = _parameters.begin(), end = _parameters.end();
+    for ( ; it != end; ++it) {
+        bool encoded = false;
+        if (matchingParameter(param, (*it).first, &encoded)) {
+            if (!encoded)
+                (*it).first.append('*');
+        }
+    }
+}
+
+static QByteArray protectedParameter(const QByteArray& value)
+{
+    static const QRegExp whitespace("\\s+");
+    static const QRegExp tspecials = QRegExp("[<>\\[\\]\\(\\)\\?:;@\\\\,=]");
+
+    if ((whitespace.indexIn(value) != -1) ||
+        (tspecials.indexIn(value) != -1))
+        return QMail::quoteString(value);
+    else
+        return value;
+}
+
+static bool extendedParameter(const QByteArray& name, QByteArray* truncated = 0, int* number = 0, bool* encoded = 0)
+{
+    QByteArray param(name.trimmed());
+
+    int index = param.indexOf('*');
+    if (index == -1)
+        return false;
+
+    if (truncated)
+        *truncated = param.left(index).trimmed();
+
+    return validExtension(param.mid(index), number, encoded);
+}
+
+QList<QMailMessageHeaderField::ParameterType> QMailMessageHeaderFieldPrivate::parameters() const
+{
+    QList<QMailMessageHeaderField::ParameterType> result;
+
+    foreach (const QMailMessageContentType::ParameterType& param, _parameters) {
+        QByteArray id;
+        int number;
+        if (extendedParameter(param.first, &id, &number)) {
+            if (number == 0) {
+                result.append(qMakePair(id, parameter(id)));
+            }
+        }
+        else {
+            result.append(param);
+        }
+    }
+
+    return result;
+}
+
+QByteArray QMailMessageHeaderFieldPrivate::toString(bool includeName, bool presentable) const
+{
+    if (_id.isEmpty())
+        return QByteArray();
+
+    QByteArray result;
+    if (includeName) {
+        result = _id + ":";
+    }
+    
+    if (!_content.isEmpty()) {
+        if (includeName)
+            result += ' ';
+        result += _content;
+    }
+
+    if (_structured)
+    {
+        foreach (const QMailMessageContentType::ParameterType& parameter, (presentable ? parameters() : _parameters))
+            result.append("; ").append(parameter.first).append('=').append(protectedParameter(parameter.second));
+    }
+
+    return result;
+}
+
+static void outputHeaderPart(QDataStream& out, const QByteArray& text, int* lineLength, const int maxLineLength)
+{
+    static const QRegExp whitespace("\\s");
+
+    int remaining = maxLineLength - *lineLength;
+    if (text.length() <= remaining)
+    {
+        out << DataString(text);
+        *lineLength += text.length();
+    }
+    else
+    {
+        // See if we can find suitable whitespace to break the line
+        int wsIndex = -1;
+        int lastIndex = -1;
+        int preferredIndex = -1;
+        do 
+        {
+            lastIndex = wsIndex;
+            if ((lastIndex > 0) && (text[lastIndex - 1] == ';')) {
+                // Prefer to split after (possible) parameters
+                preferredIndex = lastIndex;
+            }
+
+            wsIndex = whitespace.indexIn(text, wsIndex + 1);
+        } while ((wsIndex != -1) && (wsIndex < remaining));
+
+        if (preferredIndex != -1)
+            lastIndex = preferredIndex;
+
+        if (lastIndex == -1)
+        {
+            // We couldn't find any suitable whitespace - just break at the last char
+            lastIndex = remaining;
+        }
+
+        if (lastIndex == 0)
+        {
+            out << DataString('\n') << DataString(text[0]);
+            *lineLength = 1;
+            lastIndex = 1;
+        }
+        else
+        {
+            out << DataString(text.left(lastIndex)) << DataString('\n');
+
+            if (lastIndex == remaining) {
+                // We need to insert some artifical whitespace
+                out << DataString('\t');
+            } else {
+                // Append the breaking whitespace (ensure it does not get CRLF-ified)
+                out << DataString(QByteArray(1, text[lastIndex]));
+                ++lastIndex;
+            }
+
+            *lineLength = 1;
+        }
+
+        QByteArray remainder(text.mid(lastIndex));
+        if (!remainder.isEmpty())
+            outputHeaderPart(out, remainder, lineLength, maxLineLength);
+    }
+}
+
+void QMailMessageHeaderFieldPrivate::output(QDataStream& out) const
+{
+    static const int maxLineLength = 78;
+
+    if (_id.isEmpty())
+        return;
+
+    if (_structured) {
+        qWarning() << "Unable to output structured header field:" << _id;
+        return;
+    }
+
+    QByteArray element(_id);
+    element.append(':');
+    out << DataString(element);
+
+    if (!_content.isEmpty()) {
+        int lineLength = element.length();
+        outputHeaderPart(out, " " + _content, &lineLength, maxLineLength);
+    }
+
+    out << DataString('\n');
+}
+
+static bool parameterEncoded(const QByteArray& name)
+{
+    QByteArray param(name.trimmed());
+    if (param.isEmpty())
+        return false;
+
+    return (param[param.length() - 1] == '*');
+}
+
+QString QMailMessageHeaderFieldPrivate::decodedContent() const
+{
+    QString result(QMailMessageHeaderField::decodeContent(_content));
+
+    if (_structured)
+    {
+        foreach (const QMailMessageContentType::ParameterType& parameter, _parameters) {
+            QString decoded;
+            if (parameterEncoded(parameter.first))
+                decoded = QMailMessageHeaderField::decodeParameter(protectedParameter(parameter.second));
+            else
+                decoded = protectedParameter(parameter.second);
+            result.append("; ").append(parameter.first).append('=').append(decoded);
+        }
+    }
+
+    return result;
+}
+
+template <typename Stream> 
+void QMailMessageHeaderFieldPrivate::serialize(Stream &stream) const
+{
+    stream << _id;
+    stream << _content;
+    stream << _structured;
+    stream << _parameters;
+}
+
+template <typename Stream> 
+void QMailMessageHeaderFieldPrivate::deserialize(Stream &stream)
+{
+    stream >> _id;
+    stream >> _content;
+    stream >> _structured;
+    stream >> _parameters;
+}
+
+
+/*!
+    \class QMailMessageHeaderField
+
+    \brief The QMailMessageHeaderField class encapsulates the parsing of message header fields.
+    
+    \ingroup messaginglibrary
+   
+    QMailMessageHeaderField provides simplified access to the various components of the 
+    header field, and allows the field content to be extracted in a standardized form.
+
+    The content of a header field may be formed of unstructured text, or it may have an 
+    internal structure.  If a structured field is specified, QMailMessageHeaderField assumes 
+    that the contained header field is structured in a format equivalent to that used for the 
+    RFC 2045 'Content-Type' and RFC 2183 'Content-Disposition' header fields.  If the field 
+    is unstructured, or conforms to a different structure, then the parameter() and parameters() functions
+    will return empty results, and the setParameter() function will have no effect.
+
+    QMailMessageHeaderField contains static functions to assist in creating correct
+    header field content, and presenting header field content.  The encodeWord() and 
+    decodeWord() functions translate between plain text and the encoded-word specification
+    defined in RFC 2045.  The encodeParameter() and decodeParameter() functions translate
+    between plain text and the encoded-parameter format defined in RFC 2231.
+
+    The removeWhitespace() function can be used to remove irrelevant whitespace characters
+    from a string, and the removeComments() function can remove any comment sequences 
+    present, encododed according to the RFC 2822 specification.
+*/
+
+/*!
+    \typedef QMailMessageHeaderField::ImplementationType
+    \internal
+*/
+
+/*!
+    \typedef QMailMessageHeaderField::ParameterType
+    \internal
+*/
+
+/*!
+    Creates an uninitialised message header field object.
+*/
+QMailMessageHeaderField::QMailMessageHeaderField()
+    : QPrivatelyImplemented<QMailMessageHeaderFieldPrivate>(new QMailMessageHeaderFieldPrivate())
+{
+}
+
+/*!
+    Creates a message header field object from the data in \a text. If \a fieldType is 
+    QMailMessageHeaderField::StructuredField, then \a text will be parsed assuming a 
+    format equivalent to that used for the RFC 2045 'Content-Type' and 
+    RFC 2183 'Content-Disposition' header fields.
+*/
+QMailMessageHeaderField::QMailMessageHeaderField(const QByteArray& text, FieldType fieldType)
+    : QPrivatelyImplemented<QMailMessageHeaderFieldPrivate>(new QMailMessageHeaderFieldPrivate(text, (fieldType == StructuredField)))
+{
+}
+
+/*!
+    Creates a message header field object with the field id \a id and the content 
+    data in \a text.  If \a fieldType is QMailMessageHeaderField::StructuredField, 
+    then \a text will be parsed assuming a format equivalent to that used for the 
+    RFC 2045 'Content-Type' and RFC 2183 'Content-Disposition' header fields.
+*/
+QMailMessageHeaderField::QMailMessageHeaderField(const QByteArray& id, const QByteArray& text, FieldType fieldType)
+    : QPrivatelyImplemented<QMailMessageHeaderFieldPrivate>(new QMailMessageHeaderFieldPrivate(id, text, (fieldType == StructuredField)))
+{
+}
+
+/*! \internal */
+bool QMailMessageHeaderField::operator== (const QMailMessageHeaderField& other) const
+{
+    return impl(this)->operator==(*other.impl(&other));
+}
+
+/*!
+    Returns true if the header field has not been initialized.
+*/
+bool QMailMessageHeaderField::isNull() const
+{
+    return impl(this)->isNull();
+}
+
+/*!
+    Returns the ID of the header field.
+*/
+QByteArray QMailMessageHeaderField::id() const
+{
+    return impl(this)->id();
+}
+
+/*!
+    Sets the ID of the header field to \a id.
+*/
+void QMailMessageHeaderField::setId(const QByteArray& id)
+{
+    impl(this)->setId(id);
+}
+
+/*!
+    Returns the content of the header field, without any associated parameters.
+*/
+QByteArray QMailMessageHeaderField::content() const
+{
+    return impl(this)->content();
+}
+
+/*!
+    Sets the content of the header field to \a text.
+*/
+void QMailMessageHeaderField::setContent(const QByteArray& text)
+{
+    impl(this)->setContent(text);
+}
+
+/*!
+    Returns the value of the parameter with the name \a name.  
+    Name comparisons are case-insensitive.
+*/
+QByteArray QMailMessageHeaderField::parameter(const QByteArray& name) const
+{
+    return impl(this)->parameter(name);
+}
+
+/*!
+    Sets the parameter with the name \a name to have the value \a value, if present; 
+    otherwise a new parameter is appended with the supplied properties.  If \a name
+    ends with a single asterisk, the parameter will be flagged as encoded.
+
+    \sa setParameterEncoded()
+*/
+void QMailMessageHeaderField::setParameter(const QByteArray& name, const QByteArray& value)
+{
+    impl(this)->setParameter(name, value);
+}
+
+/*!
+    Returns true if the parameter with name \a name exists and is marked as encoded 
+    according to RFC 2231; otherwise returns false.  
+    Name comparisons are case-insensitive.
+*/
+bool QMailMessageHeaderField::isParameterEncoded(const QByteArray& name) const
+{
+    return impl(this)->isParameterEncoded(name);
+}
+
+/*!
+    Sets any parameters with the name \a name to be marked as encoded.
+    Name comparisons are case-insensitive.
+*/
+void QMailMessageHeaderField::setParameterEncoded(const QByteArray& name)
+{
+    impl(this)->setParameterEncoded(name);
+}
+
+/*!
+    Returns the list of parameters from the header field. For each parameter, the
+    member \c first contains the name text, and the member \c second contains the value text.
+*/
+QList<QMailMessageHeaderField::ParameterType> QMailMessageHeaderField::parameters() const
+{
+    return impl(this)->parameters();
+}
+
+/*!
+    Returns the entire header field text as a formatted string, with the name of the field
+    included if \a includeName is true.  If \a presentable is true, artifacts of RFC 2822 
+    transmission format such as parameter folding will be removed.  For example: 
+    
+    \code
+    QMailMessageHeaderField hdr;
+    hdr.setId("Content-Type");
+    hdr.setContent("text/plain");
+    hdr.setParameter("charset", "us-ascii");
+
+    QString s = hdr.toString();  // s: "Content-Type: text/plain; charset=us-ascii"
+    \endcode
+*/
+QByteArray QMailMessageHeaderField::toString(bool includeName, bool presentable) const
+{
+    return impl(this)->toString(includeName, presentable);
+}
+
+/*!
+    Returns the content of the header field as unicode text.  If the content of the
+    field contains any encoded-word or encoded-parameter values, they will be decoded on output.
+*/
+QString QMailMessageHeaderField::decodedContent() const
+{
+    return impl(this)->decodedContent();
+}
+
+/*! \internal */
+void QMailMessageHeaderField::parse(const QByteArray& text, FieldType fieldType)
+{
+    return impl(this)->parse(text, (fieldType == StructuredField));
+}
+
+/*!
+    Returns the content of the string \a input encoded into a series of RFC 2045 'encoded-word'
+    format tokens, each no longer than 75 characters.  The encoding used can be specified in 
+    \a charset, or can be deduced from the content of \a input if \a charset is empty.
+*/
+QByteArray QMailMessageHeaderField::encodeWord(const QString& input, const QByteArray& charset)
+{
+    return ::encodeWord(input, charset, 0);
+}
+
+/*!
+    Returns the content of \a input decoded from RFC 2045 'encoded-word' format.
+*/
+QString QMailMessageHeaderField::decodeWord(const QByteArray& input)
+{
+    // This could actually be a sequence of encoded words...
+    return decodeWordSequence(input);
+}
+
+/*!
+    Returns the content of the string \a input encoded into RFC 2231 'extended-parameter'
+    format.  The encoding used can be specified in \a charset, or can be deduced from the 
+    content of \a input if \a charset is empty.  If \a language is non-empty, it will be 
+    included in the encoded output; otherwise the language component will be extracted from 
+    \a charset, if it contains a trailing language specifier as defined in RFC 2231.
+*/
+QByteArray QMailMessageHeaderField::encodeParameter(const QString& input, const QByteArray& charset, const QByteArray& language)
+{
+    return ::encodeParameter(input, charset, language);
+}
+
+/*!
+    Returns the content of \a input decoded from RFC 2231 'extended-parameter' format.
+*/
+QString QMailMessageHeaderField::decodeParameter(const QByteArray& input)
+{
+    return ::decodeParameter(input);
+}
+
+/*!
+    Returns the content of the string \a input encoded into a sequence of RFC 2045 'encoded-word'
+    format tokens.  The encoding used can be specified in \a charset, or can be deduced for each
+    token read from \a input if \a charset is empty.
+*/
+QByteArray QMailMessageHeaderField::encodeContent(const QString& input, const QByteArray& charset)
+{
+    return encodeWordSequence(input, charset);
+}
+
+/*!
+    Returns the content of \a input, decoding any encountered RFC 2045 'encoded-word' format
+    tokens to unicode.
+*/
+QString QMailMessageHeaderField::decodeContent(const QByteArray& input)
+{
+    return decodeWordSequence(input);
+}
+
+/*!
+    Returns the content of \a input with any comment sections removed.
+*/
+QByteArray QMailMessageHeaderField::removeComments(const QByteArray& input)
+{
+    return ::removeComments(input, &::isprint);
+}
+
+/*!
+    Returns the content of \a input with any whitespace characters removed. 
+    Whitespace inside double quotes is preserved.
+*/
+QByteArray QMailMessageHeaderField::removeWhitespace(const QByteArray& input)
+{
+    QByteArray result;
+    result.reserve(input.length());
+
+    const char* const begin = input.constData();
+    const char* const end = begin + input.length();
+    const char* it = begin;
+    for (bool quoted = false; it != end; ++it) {
+        if (*it == '"') {
+            if ((it == begin) || (*(it - 1) != '\\'))
+                quoted = !quoted;
+        }
+        if (quoted || !isspace(*it))
+            result.append(*it);
+    }
+    
+    return result;
+}
+
+/*! \internal */
+void QMailMessageHeaderField::output(QDataStream& out) const
+{
+    impl(this)->output(out);
+}
+
+/*! 
+    \fn QMailMessageHeaderField::serialize(Stream&) const
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageHeaderField::serialize(Stream &stream) const
+{
+    impl(this)->serialize(stream);
+}
+
+/*! 
+    \fn QMailMessageHeaderField::deserialize(Stream&)
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageHeaderField::deserialize(Stream &stream)
+{
+    impl(this)->deserialize(stream);
+}
+
+
+/*!
+    \class QMailMessageContentType
+
+    \brief The QMailMessageContentType class encapsulates the parsing of the RFC 2822
+    'Content-Type' header field.
+    
+    \ingroup messaginglibrary
+   
+    QMailMessageContentType provides simplified access to the various components of the 
+    'Content-Type' header field.
+    Components of the header field not exposed by member functions can be accessed using
+    the functions inherited from QMailMessageHeaderField.
+*/
+
+/*! \internal */
+QMailMessageContentType::QMailMessageContentType()
+    : QMailMessageHeaderField("Content-Type")
+{
+}
+
+/*!
+    Creates a content type object from the data in \a type.
+*/
+QMailMessageContentType::QMailMessageContentType(const QByteArray& type)
+    : QMailMessageHeaderField("Content-Type")
+{
+    // Find the components, and create a content value from them
+    QByteArray content;
+
+    // Although a conforming CT must be: <type> "/" <subtype> without whitespace,
+    // we'll be a bit more accepting
+    int index = type.indexOf('/');
+    if (index == -1)
+    {
+        content = type.trimmed();
+    }
+    else
+    {
+        QByteArray primaryType = type.left(index).trimmed();
+        QByteArray secondaryType = type.mid(index + 1).trimmed();
+
+        content = primaryType;
+        if (!secondaryType.isEmpty())
+            content.append('/').append(secondaryType);
+    }
+
+    parse(content, StructuredField);
+}
+
+/*!
+    Creates a content type object from the content of \a field.
+*/
+QMailMessageContentType::QMailMessageContentType(const QMailMessageHeaderField& field)
+    : QMailMessageHeaderField(field)
+{
+    QMailMessageHeaderField::setId("Content-Type");
+}
+
+/*!
+    Returns the primary type information of the content type header field.
+
+    For example: if content() returns "text/plain", then type() returns "text"
+*/
+QByteArray QMailMessageContentType::type() const
+{
+    QByteArray entire = content();
+    int index = entire.indexOf('/');
+    if (index == -1)
+        return entire.trimmed();
+
+    return entire.left(index).trimmed();
+}
+
+/*!
+    Sets the primary type information of the 'Content-Type' header field to \a type. If \a type 
+    is empty, then any pre-existing sub-type information will be cleared.
+
+    \sa setSubType()
+*/
+void QMailMessageContentType::setType(const QByteArray& type)
+{
+    if (type.isEmpty())
+    {
+        // Note - if there is a sub-type, setting type to null will destroy it
+        setContent(type);
+    }
+    else
+    {
+        QByteArray content(type);
+
+        QByteArray secondaryType(subType());
+        if (!secondaryType.isEmpty())
+            content.append('/').append(secondaryType);
+
+        setContent(content);
+    }
+}
+
+/*!
+    Returns the sub-type information of the 'Content-Type' header field.
+
+    For example: if content() returns "text/plain", then subType() returns "plain"
+*/
+QByteArray QMailMessageContentType::subType() const
+{
+    QByteArray entire = content();
+    int index = entire.indexOf('/');
+    if (index == -1)
+        return QByteArray();
+
+    return entire.mid(index + 1).trimmed();
+}
+
+/*!
+    Sets the sub-type information of the 'Content-Type' header field to \a subType. If no primary
+    type has been set, then setting the sub-type has no effect.
+
+    \sa setType()
+*/
+void QMailMessageContentType::setSubType(const QByteArray& subType)
+{
+    QByteArray primaryType(type());
+    if (!primaryType.isEmpty())
+    {
+        if (!subType.isEmpty())
+            primaryType.append('/').append(subType);
+
+        setContent(primaryType);
+    }
+}
+
+/*!
+    Returns the value of the 'name' parameter, if present; otherwise returns an empty QByteArray.
+*/
+QByteArray QMailMessageContentType::name() const
+{
+    return parameter("name");
+}
+
+/*!
+    Sets the value of the 'name' parameter to \a name.
+*/
+void QMailMessageContentType::setName(const QByteArray& name)
+{
+    setParameter("name", name);
+}
+
+/*!
+    Returns the value of the 'boundary' parameter, if present; otherwise returns an empty QByteArray.
+*/
+QByteArray QMailMessageContentType::boundary() const
+{
+    QByteArray value = parameter("boundary");
+    if (value.isEmpty() || !isParameterEncoded("boundary"))
+        return value;
+
+    // The boundary is an encoded parameter.  Therefore, we need to extract the
+    // usable ascii part, since a valid message must be composed of ascii only
+    return to7BitAscii(QMailMessageHeaderField::decodeParameter(value));
+}
+
+/*!
+    Sets the value of the 'boundary' parameter to \a boundary.
+*/
+void QMailMessageContentType::setBoundary(const QByteArray& boundary)
+{
+    setParameter("boundary", boundary);
+}
+
+/*!
+    Returns the value of the 'charset' parameter, if present; otherwise returns an empty QByteArray.
+*/
+QByteArray QMailMessageContentType::charset() const
+{
+    QByteArray value = parameter("charset");
+    if (value.isEmpty() || !isParameterEncoded("charset"))
+        return value;
+
+    // The boundary is an encoded parameter.  Therefore, we need to extract the
+    // usable ascii part, since a valid charset must be composed of ascii only
+    return to7BitAscii(QMailMessageHeaderField::decodeParameter(value));
+}
+
+/*!
+    Sets the value of the 'charset' parameter to \a charset.
+*/
+void QMailMessageContentType::setCharset(const QByteArray& charset)
+{
+    setParameter("charset", charset);
+}
+
+
+/*!
+    \class QMailMessageContentDisposition
+
+    \brief The QMailMessageContentDisposition class encapsulates the parsing of the RFC 2822
+    'Content-Disposition' header field.
+    
+    \ingroup messaginglibrary
+   
+    QMailMessageContentDisposition provides simplified access to the various components of the 
+    'Content-Disposition' header field.
+    Components of the header field not exposed by member functions can be accessed using
+    the functions inherited from QMailMessageHeaderField.
+*/
+
+/*! \internal */
+QMailMessageContentDisposition::QMailMessageContentDisposition()
+    : QMailMessageHeaderField("Content-Disposition")
+{
+}
+
+/*!
+    Creates a disposition header field object from the data in \a type.
+*/
+QMailMessageContentDisposition::QMailMessageContentDisposition(const QByteArray& type)
+    : QMailMessageHeaderField("Content-Disposition", type)
+{
+}
+
+/*!
+    Creates a 'Content-Disposition' header field object with the type \a type.
+*/
+QMailMessageContentDisposition::QMailMessageContentDisposition(QMailMessageContentDisposition::DispositionType type)
+    : QMailMessageHeaderField("Content-Disposition")
+{
+    setType(type);
+}
+
+/*!
+    Creates a disposition header field object from the content of \a field.
+*/
+QMailMessageContentDisposition::QMailMessageContentDisposition(const QMailMessageHeaderField& field)
+    : QMailMessageHeaderField(field)
+{
+    QMailMessageHeaderField::setId("Content-Disposition");
+}
+
+/*!
+    Returns the disposition type of this header field.
+*/
+QMailMessageContentDisposition::DispositionType QMailMessageContentDisposition::type() const
+{
+    const QByteArray& type = content();
+
+    if (insensitiveEqual(type, "inline"))
+        return Inline;
+    else if (insensitiveEqual(type, "attachment"))
+        return Attachment;
+
+    return None;
+}
+
+/*!
+    Sets the disposition type of this field to \a type.
+*/
+void QMailMessageContentDisposition::setType(QMailMessageContentDisposition::DispositionType type)
+{
+    if (type == Inline)
+        setContent("inline");
+    else if (type == Attachment)
+        setContent("attachment");
+    else
+        setContent(QByteArray());
+}
+
+/*!
+    Returns the value of the 'filename' parameter, if present; otherwise returns an empty QByteArray.
+*/
+QByteArray QMailMessageContentDisposition::filename() const
+{
+    return parameter("filename");
+}
+
+/*!
+    Sets the value of the 'filename' parameter to \a filename.
+*/
+void QMailMessageContentDisposition::setFilename(const QByteArray& filename)
+{
+    setParameter("filename", filename);
+}
+
+/*!
+    Returns the value of the 'creation-date' parameter, if present; otherwise returns an uninitialised time stamp.
+*/
+QMailTimeStamp QMailMessageContentDisposition::creationDate() const
+{
+    return QMailTimeStamp(parameter("creation-date"));
+}
+
+/*!
+    Sets the value of the 'creation-date' parameter to \a timeStamp.
+*/
+void QMailMessageContentDisposition::setCreationDate(const QMailTimeStamp& timeStamp)
+{
+    setParameter("creation-date", to7BitAscii(timeStamp.toString()));
+}
+
+/*!
+    Returns the value of the 'modification-date' parameter, if present; otherwise returns an uninitialised time stamp.
+*/
+QMailTimeStamp QMailMessageContentDisposition::modificationDate() const
+{
+    return QMailTimeStamp(parameter("modification-date"));
+}
+
+/*!
+    Sets the value of the 'modification-date' parameter to \a timeStamp.
+*/
+void QMailMessageContentDisposition::setModificationDate(const QMailTimeStamp& timeStamp)
+{
+    setParameter("modification-date", to7BitAscii(timeStamp.toString()));
+}
+
+
+/*!
+    Returns the value of the 'read-date' parameter, if present; otherwise returns an uninitialised time stamp.
+*/
+QMailTimeStamp QMailMessageContentDisposition::readDate() const
+{
+    return QMailTimeStamp(parameter("read-date"));
+}
+
+/*!
+    Sets the value of the 'read-date' parameter to \a timeStamp.
+*/
+void QMailMessageContentDisposition::setReadDate(const QMailTimeStamp& timeStamp)
+{
+    setParameter("read-date", to7BitAscii(timeStamp.toString()));
+}
+
+/*!
+    Returns the value of the 'size' parameter, if present; otherwise returns -1.
+*/
+int QMailMessageContentDisposition::size() const
+{
+    QByteArray sizeText = parameter("size");
+
+    if (sizeText.isEmpty())
+        return -1;
+
+    return sizeText.toUInt();
+}
+
+/*!
+    Sets the value of the 'size' parameter to \a size.
+*/
+void QMailMessageContentDisposition::setSize(int size)
+{
+    setParameter("size", QByteArray::number(size));
+}
+
+
+/* QMailMessageHeader*/
+
+QMailMessageHeaderPrivate::QMailMessageHeaderPrivate()
+    : QPrivateImplementationBase(this)
+{
+}
+
+enum NewLineStatus { None, Cr, CrLf };
+
+static QList<QByteArray> parseHeaders(const QByteArray& input)
+{
+    QList<QByteArray> result;
+    QByteArray progress;
+
+    // Find each terminating newline, which must be CR, LF, then non-whitespace or end
+    NewLineStatus status = None;
+
+    const char* begin = input.constData();
+    const char* it = begin;
+    for (const char* const end = it + input.length(); it != end; ++it) {
+        if (status == CrLf) {
+            if (*it == ' ' || *it == '\t') {
+                // The CRLF was folded
+                if ((it - begin) > 2) {
+                    progress.append(QByteArray(begin, (it - begin - 2)));
+                }
+                begin = it;
+            }
+            else {
+                // That was an unescaped CRLF
+                if ((it - begin) > 2) {
+                    progress.append(QByteArray(begin, (it - begin) - 2));
+                }
+                if (!progress.isEmpty()) {
+                    // Non-empty field
+                    result.append(progress);
+                    progress.clear();
+                }
+                begin = it;
+            }
+            status = None;
+        }
+        else if (status == Cr) {
+            if (*it == QMailMessage::LineFeed) {
+                // CRLF sequence completed
+                status = CrLf;
+            }
+            else {
+                status = None;
+            }
+        }
+        else {
+            if (*it == QMailMessage::CarriageReturn)
+                status = Cr;
+        }
+    }
+
+    if (it != begin) {
+        int skip = (status == CrLf ? 2 : (status == None ? 0 : 1));
+        if ((it - begin) > skip) {
+            progress.append(QByteArray(begin, (it - begin) - skip));
+        }
+        if (!progress.isEmpty()) {
+            result.append(progress);
+        }
+    }
+
+    return result;
+}
+
+QMailMessageHeaderPrivate::QMailMessageHeaderPrivate(const QByteArray& input)
+    : QPrivateImplementationBase(this),
+      _headerFields(parseHeaders(input))
+{
+}
+
+static QByteArray fieldId(const QByteArray &id)
+{
+    QByteArray name = id.trimmed();
+    if ( !name.endsWith(':') )
+        name.append(':');
+    return name;
+}
+
+static QPair<QByteArray, QByteArray> fieldParts(const QByteArray &id, const QByteArray &content)
+{
+    QByteArray value = QByteArray(" ") + content.trimmed();
+    return qMakePair(fieldId(id), value);
+}
+
+static bool matchingId(const QByteArray& id, const QByteArray& other, bool allowPartial = false)
+{
+    QByteArray match(id.trimmed());
+
+    int index = insensitiveIndexOf(match, other);
+    if (index == -1)
+        return false;
+
+    if (index > 0)
+    {
+        // Ensure that every preceding character is whitespace
+        QByteArray leader(other.left(index).trimmed());
+        if (!leader.isEmpty())
+            return false;
+    }
+
+    if (allowPartial)
+        return true;
+
+    int lastIndex = index + match.length() - 1;
+    index = other.indexOf(':', lastIndex);
+    if (index == -1)
+        index = other.length() - 1;
+
+    // Ensure that there is only whitespace between the matched ID and the end of the ID
+    if ((index - lastIndex) > 1)
+    {
+        QByteArray trailer(other.mid(lastIndex + 1, (index - lastIndex)).trimmed());
+        if (!trailer.isEmpty())
+            return false;
+    }
+
+    return true;
+}
+
+void QMailMessageHeaderPrivate::update(const QByteArray &id, const QByteArray &content)
+{
+    QPair<QByteArray, QByteArray> parts = fieldParts(id, content);
+    QByteArray updated = parts.first + parts.second;
+
+    const QList<QByteArray>::Iterator end = _headerFields.end();
+    for (QList<QByteArray>::Iterator it = _headerFields.begin(); it != end; ++it) {
+        if ( matchingId(id, (*it)) ) {
+            *it = updated;
+            return;
+        }
+    }
+
+    // new header field, add it
+    _headerFields.append( updated );
+}
+
+void QMailMessageHeaderPrivate::append(const QByteArray &id, const QByteArray &content)
+{
+    QPair<QByteArray, QByteArray> parts = fieldParts(id, content);
+    _headerFields.append( parts.first + parts.second );
+}
+
+void QMailMessageHeaderPrivate::remove(const QByteArray &id)
+{
+    QList<QList<QByteArray>::Iterator> matches;
+
+    const QList<QByteArray>::Iterator end = _headerFields.end();
+    for (QList<QByteArray>::Iterator it = _headerFields.begin(); it != end; ++it) {
+        if ( matchingId(id, (*it)) )
+            matches.prepend(it);
+    }
+
+    foreach (QList<QByteArray>::Iterator it, matches)
+        _headerFields.erase(it);
+}
+
+QList<QMailMessageHeaderField> QMailMessageHeaderPrivate::fields(const QByteArray& id, int maximum) const
+{
+    QList<QMailMessageHeaderField> result;
+
+    foreach (const QByteArray& field, _headerFields) {
+        QMailMessageHeaderField headerField(field, QMailMessageHeaderField::UnstructuredField);
+        if ( matchingId(id, headerField.id()) ) {
+            result.append(headerField);
+            if (maximum > 0 && result.count() == maximum)
+                return result;
+        }
+    }
+
+    return result;
+}
+
+void QMailMessageHeaderPrivate::output(QDataStream& out, const QList<QByteArray>& exclusions, bool excludeInternalFields) const
+{
+    foreach (const QByteArray& field, _headerFields) {
+        QMailMessageHeaderField headerField(field, QMailMessageHeaderField::UnstructuredField);
+        const QByteArray& id = headerField.id();
+        bool excluded = false;
+
+        // Bypass any header field that has the internal prefix
+        if (excludeInternalFields)
+            excluded = matchingId(internalPrefix(), id, true);
+
+        // Bypass any header in the list of exclusions
+        if (!excluded)
+            foreach (const QByteArray& exclusion, exclusions)
+                if (matchingId(exclusion, id))
+                    excluded = true;
+
+        if (!excluded)
+            headerField.output(out);
+    }
+}
+
+template <typename Stream> 
+void QMailMessageHeaderPrivate::serialize(Stream &stream) const
+{
+    stream << _headerFields;
+}
+
+template <typename Stream> 
+void QMailMessageHeaderPrivate::deserialize(Stream &stream)
+{
+    stream >> _headerFields;
+}
+
+
+/*!
+    \class QMailMessageHeader
+    \internal
+*/
+
+QMailMessageHeader::QMailMessageHeader()
+    : QPrivatelyImplemented<QMailMessageHeaderPrivate>(new QMailMessageHeaderPrivate())
+{
+}
+
+QMailMessageHeader::QMailMessageHeader(const QByteArray& input)
+    : QPrivatelyImplemented<QMailMessageHeaderPrivate>(new QMailMessageHeaderPrivate(input))
+{
+}
+
+void QMailMessageHeader::update(const QByteArray &id, const QByteArray &content)
+{
+    impl(this)->update(id, content);
+}
+
+void QMailMessageHeader::append(const QByteArray &id, const QByteArray &content)
+{
+    impl(this)->append(id, content);
+}
+
+void QMailMessageHeader::remove(const QByteArray &id)
+{
+    impl(this)->remove(id);
+}
+
+QMailMessageHeaderField QMailMessageHeader::field(const QByteArray& id) const
+{
+    QList<QMailMessageHeaderField> result = impl(this)->fields(id, 1);
+    if (result.count())
+        return result[0];
+
+    return QMailMessageHeaderField();
+}
+
+QList<QMailMessageHeaderField> QMailMessageHeader::fields(const QByteArray& id) const
+{
+    return impl(this)->fields(id);
+}
+
+QList<const QByteArray*> QMailMessageHeader::fieldList() const
+{
+    QList<const QByteArray*> result;
+
+    QList<QByteArray>::ConstIterator const end = impl(this)->_headerFields.end();
+    for (QList<QByteArray>::ConstIterator it = impl(this)->_headerFields.begin(); it != end; ++it)
+        result.append(&(*it));
+
+    return result;
+}
+
+void QMailMessageHeader::output(QDataStream& out, const QList<QByteArray>& exclusions, bool excludeInternalFields) const
+{
+    impl(this)->output(out, exclusions, excludeInternalFields);
+}
+
+/*! 
+    \fn QMailMessageHeader::serialize(Stream&) const
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageHeader::serialize(Stream &stream) const
+{
+    impl(this)->serialize(stream);
+}
+
+/*! 
+    \fn QMailMessageHeader::deserialize(Stream&)
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageHeader::deserialize(Stream &stream)
+{
+    impl(this)->deserialize(stream);
+}
+
+
+/* QMailMessageBody */
+
+QMailMessageBodyPrivate::QMailMessageBodyPrivate()
+    : QPrivateImplementationBase(this)
+{
+    // Default encoding
+    _encoding = QMailMessageBody::SevenBit;
+}
+
+void QMailMessageBodyPrivate::fromLongString(LongString& ls, const QMailMessageContentType& content, QMailMessageBody::TransferEncoding te, QMailMessageBody::EncodingStatus status)
+{
+    _encoding = te;
+    _type = content;
+    _encoded = (status == QMailMessageBody::AlreadyEncoded);
+    _filename = QString();
+    _bodyData = ls;
+}
+
+void QMailMessageBodyPrivate::fromFile(const QString& file, const QMailMessageContentType& content, QMailMessageBody::TransferEncoding te, QMailMessageBody::EncodingStatus status)
+{
+    _encoding = te;
+    _type = content;
+    _encoded = (status == QMailMessageBody::AlreadyEncoded);
+    _filename = file;
+    _bodyData = LongString(file);
+}
+
+void QMailMessageBodyPrivate::fromStream(QDataStream& in, const QMailMessageContentType& content, QMailMessageBody::TransferEncoding te, QMailMessageBody::EncodingStatus status)
+{
+    _encoding = te;
+    _type = content;
+    _encoded = true;
+    _filename = QString();
+    _bodyData = LongString();
+    
+    // If the data is already encoded, we don't need to do it again
+    if (status == QMailMessageBody::AlreadyEncoded)
+        te = QMailMessageBody::SevenBit;
+
+    QMailCodec* codec = codecForEncoding(te, content);
+    if (codec)
+    {
+        // Stream to the buffer, encoding as required
+        QByteArray encoded;
+        {
+            QDataStream out(&encoded, QIODevice::WriteOnly);
+            codec->encode(out, in);
+        }
+        _bodyData = LongString(encoded);
+        delete codec;
+    }
+}
+
+void QMailMessageBodyPrivate::fromStream(QTextStream& in, const QMailMessageContentType& content, QMailMessageBody::TransferEncoding te)
+{
+    _encoding = te;
+    _type = content;
+    _encoded = true;
+    _filename = QString();
+    _bodyData = LongString();
+
+    QMailCodec* codec = codecForEncoding(te, content);
+    if (codec)
+    {
+        QByteArray encoded;
+        {
+            QDataStream out(&encoded, QIODevice::WriteOnly);
+
+            // Convert the unicode string to a byte-stream, via the nominated character set
+            QString charset = _type.charset();
+
+            // If no character set is specified - treat the data as UTF-8; since it is
+            // textual data, it must have some character set...
+            if (charset.isEmpty())
+                charset = "UTF-8";
+
+            codec->encode(out, in, charset);
+        }
+        _bodyData = LongString(encoded);
+        delete codec;
+    }
+}
+
+static bool unicodeConvertingCharset(const QByteArray& charset)
+{
+    // See if this is a unicode-capable codec
+    if (QTextCodec* textCodec = codecForName(charset, true))
+    {
+        const QChar multiByteChar = 0x1234;
+        return textCodec->canEncode(multiByteChar);
+    }
+    else
+    {
+        qWarning() << "unicodeConvertingCharset: unable to find codec for charset:" << charset;
+    }
+
+    return false;
+}
+
+static QByteArray extractionCharset(const QMailMessageContentType& type)
+{
+    QByteArray charset;
+
+    // Find the charset for this data, if it is text data
+    if (insensitiveEqual(type.type(), "text"))
+    {
+        charset = type.charset();
+        if (!charset.isEmpty())
+        {
+            // If the codec can't handle multi-byte characters, don't extract to/from unicode
+            if (!unicodeConvertingCharset(charset))
+                charset = QByteArray();
+        }
+    }
+
+    return charset;
+}
+
+bool QMailMessageBodyPrivate::toFile(const QString& file, QMailMessageBody::EncodingFormat format) const
+{
+    QFile outFile(file);
+    if (!outFile.open(QIODevice::WriteOnly))
+    {
+        qWarning() << "Unable to open for write:" << file;
+        return false;
+    }
+
+    bool encodeOutput = (format == QMailMessageBody::Encoded);
+
+    // Find the charset for this data, if it is text data
+    QByteArray charset(extractionCharset(_type));
+
+    QMailMessageBody::TransferEncoding te = _encoding;
+
+    // If our data is in the required condition, we don't need to encode/decode
+    if (encodeOutput == _encoded)
+        te = QMailMessageBody::Binary;
+
+    QMailCodec* codec = codecForEncoding(te, _type);
+    if (codec)
+    {
+        bool result = false;
+
+        // Empty charset indicates no unicode encoding; encoded return data means binary streams
+        if (charset.isEmpty() || encodeOutput)
+        {
+            // We are dealing with binary data
+            QDataStream out(&outFile);
+            QDataStream* in = _bodyData.dataStream();
+            if (encodeOutput)
+                codec->encode(out, *in);
+            else
+                codec->decode(out, *in);
+            result = (in->status() == QDataStream::Ok);
+            delete in;
+        }
+        else // we should probably check that charset matches this->charset
+        {
+            // We are dealing with unicode text data, which we want in unencoded form
+            QTextStream out(&outFile);
+            out.setCodec(charset);
+
+            // If the content is unencoded we can pass it back via a text stream
+            if (!_encoded)
+            {
+                QTextStream* in = _bodyData.textStream();
+                in->setCodec(charset);
+                QMailCodec::copy(out, *in);
+                result = (in->status() == QTextStream::Ok);
+                delete in;
+            }
+            else
+            {
+                QDataStream* in = _bodyData.dataStream();
+                codec->decode(out, *in, charset);
+                result = (in->status() == QDataStream::Ok);
+                delete in;
+            }
+        }
+
+        delete codec;
+        return result;
+    }
+
+    return false;
+}
+
+bool QMailMessageBodyPrivate::toStream(QDataStream& out, QMailMessageBody::EncodingFormat format) const
+{
+    bool encodeOutput = (format == QMailMessageBody::Encoded);
+    QMailMessageBody::TransferEncoding te = _encoding;
+
+    // If our data is in the required condition, we don't need to encode/decode
+    if (encodeOutput == _encoded)
+        te = QMailMessageBody::Binary;
+
+    QMailCodec* codec = codecForEncoding(te, _type);
+    if (codec)
+    {
+        bool result = false;
+
+        QByteArray charset(extractionCharset(_type));
+        if (!charset.isEmpty() && !_filename.isEmpty() && encodeOutput)
+        {
+            // This data must be unicode in the file
+            QTextStream* in = _bodyData.textStream();
+            in->setCodec(charset);
+            codec->encode(out, *in, charset);
+            result = (in->status() == QTextStream::Ok);
+            delete in;
+        }
+        else
+        {
+            QDataStream* in = _bodyData.dataStream();
+            if (encodeOutput)
+                codec->encode(out, *in);
+            else
+                codec->decode(out, *in);
+            result = (in->status() == QDataStream::Ok);
+            delete in;
+        }
+
+        delete codec;
+        return result;
+    }
+
+    return false;
+}
+
+bool QMailMessageBodyPrivate::toStream(QTextStream& out) const
+{
+    QByteArray charset = _type.charset();
+    if (charset.isEmpty() || (insensitiveIndexOf("ascii", charset) != -1)) {
+        // We'll assume the text is plain ASCII, to be extracted to Latin-1
+        charset = "ISO-8859-1";
+    }
+
+    out.setCodec(charset);
+
+    QMailMessageBody::TransferEncoding te = _encoding;
+
+    // If our data is not encoded, we don't need to decode
+    if (!_encoded)
+        te = QMailMessageBody::Binary;
+
+    QMailCodec* codec = codecForEncoding(te, _type);
+    if (codec)
+    {
+        bool result = false;
+
+        if (!_encoded && !_filename.isEmpty() && unicodeConvertingCharset(charset))
+        { 
+            // The data is already in unicode format
+            QTextStream* in = _bodyData.textStream();
+            in->setCodec(charset);
+            QMailCodec::copy(out, *in);
+            result = (in->status() == QTextStream::Ok);
+            delete in;
+        }
+        else
+        {
+            // Write the data to out, decoding if necessary
+            QDataStream* in = _bodyData.dataStream();
+            codec->decode(out, *in, charset);
+            result = (in->status() == QDataStream::Ok);
+            delete in;
+        }
+
+        delete codec;
+        return result;
+    }
+
+    return false;
+}
+
+QMailMessageContentType QMailMessageBodyPrivate::contentType() const
+{
+    return _type;
+}
+
+QMailMessageBody::TransferEncoding QMailMessageBodyPrivate::transferEncoding() const
+{
+    return _encoding;
+}
+
+bool QMailMessageBodyPrivate::isEmpty() const
+{
+    return _bodyData.isEmpty();
+}
+
+int QMailMessageBodyPrivate::length() const
+{
+    return _bodyData.length();
+}
+
+uint QMailMessageBodyPrivate::indicativeSize() const
+{
+    return (_bodyData.length() / IndicativeSizeUnit);
+}
+
+void QMailMessageBodyPrivate::output(QDataStream& out, bool includeAttachments) const
+{
+    if ( includeAttachments )
+        toStream( out, QMailMessageBody::Encoded );
+}
+
+template <typename Stream> 
+void QMailMessageBodyPrivate::serialize(Stream &stream) const
+{
+    stream << _encoding;
+    stream << _bodyData;
+    stream << _filename;
+    stream << _encoded;
+    stream << _type;
+}
+
+template <typename Stream> 
+void QMailMessageBodyPrivate::deserialize(Stream &stream)
+{
+    stream >> _encoding;
+    stream >> _bodyData;
+    stream >> _filename;
+    stream >> _encoded;
+    stream >> _type;
+}
+
+
+/*!
+    \class QMailMessageBody
+
+    \brief The QMailMessageBody class contains the body element of a message or message part.
+    
+    \ingroup messaginglibrary
+   
+    The body of a message or message part is treated as an atomic unit by the Qt Extended messaging library.  It can only be inserted into a message part container or extracted
+    from one.  It can be inserted or extracted using either a QByteArray, a QDataStream
+    or to/from a file.  In the case of unicode text data, the insertion and extraction can
+    operate on either a QString, a QTextStream or to/from a file.
+
+    The body data must be associated with a QMailMessageContentType describing that data.
+    When extracting body data from a message or part to unicode text, the content type
+    description must include a parameter named 'charset'; this parameter is used to locate
+    a QTextCodec to be used to extract unicode data from the body data octet stream.  
+    
+    If the Content-Type of the data is a subtype of "text", then line-ending translation 
+    will be used to ensure that the text is transmitted with CR/LF line endings.  The text 
+    data supplied to QMailMessageBody must conform to the RFC 2822 restrictions on maximum 
+    line lengths: "Each line of characters MUST be no more than 998 characters, and SHOULD 
+    be no more than 78 characters, excluding the CRLF."  Textual message body data decoded 
+    from a QMailMessageBody object will have transmitted CR/LF line endings converted to 
+    \c \n on extraction.
+
+    The body data can also be encoded from 8-bit octets to 7-bit ASCII characters for
+    safe transmission through obsolete email systems.  When creating an instance of the 
+    QMailMessageBody class, the encoding to be used must be specified using the 
+    QMailMessageBody::TransferEncoding enum.
+
+    \sa QMailMessagePart, QMailMessage, QTextCodec
+*/    
+
+/*!
+    \typedef QMailMessageBody::ImplementationType
+    \internal
+*/
+
+/*! 
+    Creates an instance of QMailMessageBody.
+*/
+QMailMessageBody::QMailMessageBody()
+    : QPrivatelyImplemented<QMailMessageBodyPrivate>(new QMailMessageBodyPrivate())
+{
+}
+
+/*!
+    Creates a message body from the data contained in the file \a filename, having the content type 
+    \a type.  If \a status is QMailMessageBody::RequiresEncoding, the data from the file will be 
+    encoded to \a encoding for transmission; otherwise it must already be in that encoding, which
+    will be reported to recipients of the data.
+
+    If \a type is a subtype of "text", the data will be treated as text, and line-ending
+    translation will be employed.  Otherwise, the file will be treated as containing binary 
+    data.  If the file contains unicode text data, it will be converted to an octet stream using
+    a QTextCodec object identified by the 'charset' parameter of \a type.
+
+    If \a encoding is QMailMessageBody::QuotedPrintable, encoding will be performed assuming
+    conformance to RFC 2045.
+
+    Note that the data is not actually read from the file until it is requested by another function.
+
+    \sa QMailCodec, QMailQuotedPrintableCodec, QMailMessageContentType, QTextCodec
+*/
+QMailMessageBody QMailMessageBody::fromFile(const QString& filename, const QMailMessageContentType& type, TransferEncoding encoding, EncodingStatus status)
+{
+    QMailMessageBody body;
+    body.impl<QMailMessageBodyPrivate>()->fromFile(filename, type, encoding, status);
+    return body;
+}
+
+/*!
+    Creates a message body from the data read from \a in, having the content type \a type.  
+    If \a status is QMailMessageBody::RequiresEncoding, the data from the file will be 
+    encoded to \a encoding for transmission; otherwise it must already be in that encoding, 
+    which will be reported to recipients of the data.
+
+    If \a type is a subtype of "text", the data will be treated as text, and line-ending
+    translation will be employed.  Otherwise, the file will be treated as containing binary 
+    data.
+
+    If \a encoding is QMailMessageBody::QuotedPrintable, encoding will be performed assuming
+    conformance to RFC 2045.
+
+    \sa QMailCodec, QMailQuotedPrintableCodec
+*/
+QMailMessageBody QMailMessageBody::fromStream(QDataStream& in, const QMailMessageContentType& type, TransferEncoding encoding, EncodingStatus status)
+{
+    QMailMessageBody body;
+    body.impl<QMailMessageBodyPrivate>()->fromStream(in, type, encoding, status);
+    return body;
+}
+
+/*!
+    Creates a message body from the data contained in \a input, having the content type 
+    \a type.  If \a status is QMailMessageBody::RequiresEncoding, the data from the file will be 
+    encoded to \a encoding for transmission; otherwise it must already be in that encoding, 
+    which will be reported to recipients of the data.
+
+    If \a type is a subtype of "text", the data will be treated as text, and line-ending
+    translation will be employed.  Otherwise, the file will be treated as containing binary 
+    data.
+
+    If \a encoding is QMailMessageBody::QuotedPrintable, encoding will be performed assuming
+    conformance to RFC 2045.
+
+    \sa QMailCodec, QMailQuotedPrintableCodec
+*/
+QMailMessageBody QMailMessageBody::fromData(const QByteArray& input, const QMailMessageContentType& type, TransferEncoding encoding, EncodingStatus status)
+{
+    QMailMessageBody body;
+    {
+        QDataStream in(input);
+        body.impl<QMailMessageBodyPrivate>()->fromStream(in, type, encoding, status);
+    }
+    return body;
+}
+
+/*!
+    Creates a message body from the data read from \a in, having the content type \a type.  
+    The data read from \a in will be encoded to \a encoding for transmission, and line-ending
+    translation will be employed.  The unicode text data will be converted to an octet stream 
+    using a QTextCodec object identified by the 'charset' parameter of \a type.
+
+    If \a encoding is QMailMessageBody::QuotedPrintable, encoding will be performed assuming
+    conformance to RFC 2045.
+
+    \sa QMailCodec, QMailQuotedPrintableCodec, QMailMessageContentType, QTextCodec
+*/
+QMailMessageBody QMailMessageBody::fromStream(QTextStream& in, const QMailMessageContentType& type, TransferEncoding encoding)
+{
+    QMailMessageBody body;
+    body.impl<QMailMessageBodyPrivate>()->fromStream(in, type, encoding);
+    return body;
+}
+
+/*!
+    Creates a message body from the data contained in \a input, having the content type 
+    \a type.  The data from \a input will be encoded to \a encoding for transmission, and 
+    line-ending translation will be employed.  The unicode text data will be converted to 
+    an octet stream using a QTextCodec object identified by the 'charset' parameter of \a type.
+
+    If \a encoding is QMailMessageBody::QuotedPrintable, encoding will be performed assuming
+    conformance to RFC 2045.
+
+    \sa QMailCodec, QMailMessageContentType, QTextCodec
+*/
+QMailMessageBody QMailMessageBody::fromData(const QString& input, const QMailMessageContentType& type, TransferEncoding encoding)
+{
+    QMailMessageBody body;
+    {
+        QTextStream in(const_cast<QString*>(&input), QIODevice::ReadOnly);
+        body.impl<QMailMessageBodyPrivate>()->fromStream(in, type, encoding);
+    }
+    return body;
+}
+
+QMailMessageBody QMailMessageBody::fromLongString(LongString& ls, const QMailMessageContentType& type, TransferEncoding encoding, EncodingStatus status)
+{
+    QMailMessageBody body;
+    {
+        body.impl<QMailMessageBodyPrivate>()->fromLongString(ls, type, encoding, status);
+    }
+    return body;
+}
+
+/*!
+    Writes the data of the message body to the file named \a filename.  If \a format is
+    QMailMessageBody::Encoded, then the data is written in the transfer encoding it was
+    created with; otherwise, it is written in unencoded form.
+
+    If the body has a content type with a QMailMessageContentType::type() of "text", and the 
+    content type parameter 'charset' is not empty, then the unencoded data will be written 
+    as unicode text data, using the charset parameter to locate the appropriate QTextCodec.
+
+    Returns false if the operation causes an error; otherwise returns true.
+
+    \sa QMailCodec, QMailMessageContentType, QTextCodec
+*/
+bool QMailMessageBody::toFile(const QString& filename, EncodingFormat format) const
+{
+    return impl(this)->toFile(filename, format);
+}
+
+/*!
+    Returns the data of the message body as a QByteArray.  If \a format is
+    QMailMessageBody::Encoded, then the data is written in the transfer encoding it was
+    created with; otherwise, it is written in unencoded form.
+
+    \sa QMailCodec
+*/
+QByteArray QMailMessageBody::data(EncodingFormat format) const
+{
+    QByteArray result;
+    {
+        QDataStream out(&result, QIODevice::WriteOnly);
+        impl(this)->toStream(out, format);
+    }
+    return result;
+}
+
+/*!
+    Writes the data of the message body to the stream \a out. If \a format is
+    QMailMessageBody::Encoded, then the data is written in the transfer encoding it was
+    created with; otherwise, it is written in unencoded form.
+
+    Returns false if the operation causes an error; otherwise returns true.
+
+    \sa QMailCodec
+*/
+bool QMailMessageBody::toStream(QDataStream& out, EncodingFormat format) const
+{
+    return impl(this)->toStream(out, format);
+}
+
+/*!
+    Returns the data of the message body as a QString, in unencoded form.  Line-endings
+    transmitted as CR/LF pairs are converted to \c \n on extraction.
+
+    The 'charset' parameter of the body's content type is used to locate the appropriate 
+    QTextCodec to convert the data from an octet stream to unicode, if necessary.
+
+    \sa QMailCodec, QMailMessageContentType, QTextCodec
+*/
+QString QMailMessageBody::data() const
+{
+    QString result;
+    {
+        QTextStream out(&result, QIODevice::WriteOnly);
+        impl(this)->toStream(out);
+    }
+    return result;
+}
+
+/*!
+    Writes the data of the message body to the stream \a out, in unencoded form. 
+    Line-endings transmitted as CR/LF pairs are converted to \c \n on extraction.
+    Returns false if the operation causes an error; otherwise returns true.
+
+    The 'charset' parameter of the body's content type is used to locate the appropriate 
+    QTextCodec to convert the data from an octet stream to unicode, if necessary.
+
+    \sa QMailCodec, QMailMessageContentType, QTextCodec
+*/
+bool QMailMessageBody::toStream(QTextStream& out) const
+{
+    return impl(this)->toStream(out);
+}
+
+/*!
+    Returns the content type that the body was created with.
+*/
+QMailMessageContentType QMailMessageBody::contentType() const
+{
+    return impl(this)->contentType();
+}
+
+/*!
+    Returns the transfer encoding type that the body was created with.
+*/
+QMailMessageBody::TransferEncoding QMailMessageBody::transferEncoding() const
+{
+    return impl(this)->transferEncoding();
+}
+
+/*!
+    Returns true if the body does not contain any data.
+*/
+bool QMailMessageBody::isEmpty() const
+{
+    return impl(this)->isEmpty();
+}
+
+/*!
+    Returns the length of the body data in bytes.
+*/
+int QMailMessageBody::length() const
+{
+    return impl(this)->length();
+}
+
+/*! \internal */
+uint QMailMessageBody::indicativeSize() const
+{
+    return impl(this)->indicativeSize();
+}
+
+/*! \internal */
+void QMailMessageBody::output(QDataStream& out, bool includeAttachments) const
+{
+    impl(this)->output(out, includeAttachments);
+}
+
+/*! 
+    \fn QMailMessageBody::serialize(Stream&) const
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageBody::serialize(Stream &stream) const
+{
+    impl(this)->serialize(stream);
+}
+
+/*! 
+    \fn QMailMessageBody::deserialize(Stream&)
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageBody::deserialize(Stream &stream)
+{
+    impl(this)->deserialize(stream);
+}
+
+
+class QMailMessagePart::LocationPrivate
+{
+public:
+    QMailMessageId _messageId;
+    QList<uint> _indices;
+};
+
+
+/* QMailMessagePartContainer */
+
+template<typename Derived>
+QMailMessagePartContainerPrivate::QMailMessagePartContainerPrivate(Derived* p)
+    : QPrivateImplementationBase(p)
+{
+    _multipartType = QMailMessagePartContainer::MultipartNone;
+    _hasBody = false;
+    _dirty = false;
+}
+
+void QMailMessagePartContainerPrivate::setLocation(const QMailMessageId& id, const QList<uint>& indices)
+{
+    _messageId = id;
+    _indices = indices;
+
+    if (!_messageParts.isEmpty()) {
+        QList<QMailMessagePart>::iterator it = _messageParts.begin(), end = _messageParts.end();
+        for (uint i = 0; it != end; ++it, ++i) {
+            QList<uint> location(_indices);
+            location.append(i + 1);
+            (*it).impl<QMailMessagePartContainerPrivate>()->setLocation(_messageId, location);
+        }
+    }
+}
+
+int QMailMessagePartContainerPrivate::partNumber() const
+{
+    return (_indices.last() - 1);
+}
+
+bool QMailMessagePartContainerPrivate::contains(const QMailMessagePart::Location& location) const
+{
+    const QMailMessagePart* part = 0; 
+    const QList<QMailMessagePart>* partList = &_messageParts; 
+
+    foreach (uint index, location.d->_indices) {
+        if (partList->count() < index) {
+            return false;
+        }
+
+        part = &(partList->at(index - 1));
+        partList = &(part->impl<const QMailMessagePartContainerPrivate>()->_messageParts);
+    }
+
+    return true;
+}
+
+const QMailMessagePart& QMailMessagePartContainerPrivate::partAt(const QMailMessagePart::Location& location) const
+{
+    const QMailMessagePart* part = 0; 
+    const QList<QMailMessagePart>* partList = &_messageParts; 
+
+    foreach (uint index, location.d->_indices) {
+        part = &(partList->at(index - 1));
+        partList = &(part->impl<const QMailMessagePartContainerPrivate>()->_messageParts);
+    }
+
+    Q_ASSERT(part);
+    return *part;
+}
+
+QMailMessagePart& QMailMessagePartContainerPrivate::partAt(const QMailMessagePart::Location& location)
+{
+    QMailMessagePart* part = 0; 
+    QList<QMailMessagePart>* partList = &_messageParts; 
+
+    foreach (uint index, location.d->_indices) {
+        part = &((*partList)[index - 1]);
+        partList = &(part->impl<QMailMessagePartContainerPrivate>()->_messageParts);
+    }
+
+    return *part;
+}
+
+void QMailMessagePartContainerPrivate::setHeader(const QMailMessageHeader& partHeader, const QMailMessagePartContainerPrivate* parent)
+{
+    _header = partHeader;
+
+    defaultContentType(parent);
+
+    QByteArray contentType = headerField("Content-Type");
+    if (!contentType.isEmpty())
+    {
+        // Extract the stored parts from the supplied field
+        QMailMessageContentType type(contentType);
+        _multipartType = QMailMessagePartContainer::multipartTypeForName(type.content());
+        _boundary = type.boundary();
+    }
+}
+
+void QMailMessagePartContainerPrivate::defaultContentType(const QMailMessagePartContainerPrivate* parent)
+{
+    QMailMessageContentType type;
+
+    // Find the content-type, or use default values
+    QByteArray contentType = headerField("Content-Type");
+    bool useDefault = contentType.isEmpty();
+
+    if (!useDefault)
+    {
+        type = QMailMessageContentType(contentType);
+
+        if (type.type().isEmpty() || type.subType().isEmpty())
+        {
+            useDefault = true;
+        }
+        else if (insensitiveEqual(type.content(), "application/octet-stream"))
+        {
+            // Sender's client might not know what type, but maybe we do. Try...
+            QByteArray contentDisposition = headerField("Content-Disposition");
+            if (!contentDisposition.isEmpty())
+            {
+                QMailMessageContentDisposition disposition(contentDisposition);
+
+                QString mimeType = QMail::mimeTypeFromFileName(disposition.filename());
+                if (!mimeType.isEmpty())
+                {
+                    type.setContent(to7BitAscii(mimeType));
+                    updateHeaderField(type.id(), type.toString(false, false));
+                }
+            }
+        }
+    }
+
+    if (useDefault && parent)
+    {
+        // Note that the default is 'message/rfc822' when the parent is 'multipart/digest'
+        QMailMessageContentType parentType = parent->contentType();
+        if (parentType.content().toLower() == "multipart/digest")
+        {
+            type.setType("message");
+            type.setSubType("rfc822");
+            updateHeaderField(type.id(), type.toString(false, false));
+            useDefault = false;
+        }
+    }
+
+    if (useDefault)
+    {
+        type.setType("text");
+        type.setSubType("plain");
+        type.setCharset("us-ascii");
+        updateHeaderField(type.id(), type.toString(false, false));
+    }
+}
+
+/*! \internal */
+uint QMailMessagePartContainerPrivate::indicativeSize() const
+{
+    uint size = 0;
+
+    if (hasBody()) {
+        size = body().indicativeSize();
+    } else {
+        for (int i = 0; i < _messageParts.count(); ++i)
+            size += _messageParts[i].indicativeSize();
+    }
+
+    return size;
+}
+
+template <typename F>
+void QMailMessagePartContainerPrivate::outputParts(QDataStream **out, bool addMimePreamble, bool includeAttachments, bool excludeInternalFields, F *func) const
+{
+    static const DataString newLine('\n');
+    static const DataString marker("--");
+
+    if (_multipartType == QMailMessagePartContainer::MultipartNone)
+        return;
+
+    if (addMimePreamble) {
+        // This is a preamble (not for conformance, to assist readibility on non-conforming renderers):
+        **out << DataString("This is a multipart message in Mime 1.0 format"); // No tr
+        **out << newLine;
+    }
+
+    for ( int i = 0; i < _messageParts.count(); i++ ) {
+        **out << newLine << marker << DataString(_boundary) << newLine;
+
+        QMailMessagePart& part = const_cast<QMailMessagePart&>(_messageParts[i]);
+
+        if (part.multipartType() != QMailMessagePartContainer::MultipartNone) {
+            const QString &partBoundary(part.boundary());
+
+            if (partBoundary.isEmpty()) {
+                QString subBoundary(_boundary);
+                int index = subBoundary.indexOf(':');
+                if (index != -1) {
+                    subBoundary.insert(index, QString::number(part.partNumber()).prepend("-"));
+                } else {
+                    // Shouldn't happen...
+                    subBoundary.insert(0, QString::number(part.partNumber()).append(":"));
+                }
+
+                part.setBoundary(to7BitAscii(subBoundary));
+            }
+        }
+        QMailMessagePartPrivate *partImpl(part.impl<QMailMessagePartPrivate>());
+        partImpl->output<F>(out, false, includeAttachments, excludeInternalFields, func);
+    }
+
+    **out << newLine << marker << DataString(_boundary) << marker << newLine;
+}
+
+void QMailMessagePartContainerPrivate::outputBody(QDataStream& out, bool includeAttachments) const
+{
+    _body.output(out, includeAttachments);
+}
+
+static QString decodedContent(const QString& id, const QByteArray& content)
+{
+    // TODO: Potentially, we should disallow decoding here based on the specific header field
+    bool permitDecoding(true);
+    //QByteArray id(fieldId(to7BitAscii(id)));
+    Q_UNUSED(id)
+
+    return (permitDecoding ? QMailMessageHeaderField::decodeContent(content) : QString(content));
+}
+
+/*!
+    Returns the text of the first header field with the given \a id.
+*/
+QString QMailMessagePartContainerPrivate::headerFieldText( const QString &id ) const
+{
+    const QByteArray& content = headerField( to7BitAscii(id) );
+    return decodedContent( id, content );
+}
+
+static QMailMessageContentType updateContentType(const QByteArray& existing, QMailMessagePartContainer::MultipartType multipartType, const QByteArray& boundary)
+{
+    // Ensure that any parameters of the existing field are preserved
+    QMailMessageContentType existingType(existing);
+    QList<QMailMessageHeaderField::ParameterType> parameters = existingType.parameters();
+
+    QMailMessageContentType type(QMailMessagePartContainer::nameForMultipartType(multipartType));
+    foreach (const QMailMessageHeaderField::ParameterType& param, parameters)
+        type.setParameter(param.first, param.second);
+
+    if (!boundary.isEmpty())
+        type.setBoundary(boundary);
+
+    return type;
+}
+
+void QMailMessagePartContainerPrivate::setMultipartType(QMailMessagePartContainer::MultipartType type)
+{
+    // TODO: Is there any value in keeping _multipartType and _boundary externally from
+    // Content-type header field?
+
+    if (_multipartType != type) {
+        _multipartType = type;
+        setDirty();
+
+        if (_multipartType == QMailMessagePartContainer::MultipartNone) {
+            removeHeaderField("Content-Type");
+        } else  {
+            QMailMessageContentType contentType = updateContentType(headerField("Content-Type"), _multipartType, _boundary);
+            updateHeaderField("Content-Type", contentType.toString(false, false));
+
+            if (_hasBody) {
+                _body = QMailMessageBody();
+                _hasBody = false;
+            }
+        }
+    }
+}
+
+QByteArray QMailMessagePartContainerPrivate::boundary() const
+{
+    return _boundary;
+}
+
+void QMailMessagePartContainerPrivate::setBoundary(const QByteArray& text)
+{
+    _boundary = text;
+
+    if (_multipartType != QMailMessagePartContainer::MultipartNone) {
+        QMailMessageContentType type = updateContentType(headerField("Content-Type"), _multipartType, _boundary);
+        updateHeaderField("Content-Type", type.toString(false, false));
+    } else {
+        QMailMessageHeaderField type("Content-Type", headerField("Content-Type"));
+        type.setParameter("boundary", _boundary);
+        updateHeaderField("Content-Type", type.toString(false, false));
+    }
+}
+
+QMailMessageBody& QMailMessagePartContainerPrivate::body()
+{
+    return _body;
+}
+
+const QMailMessageBody& QMailMessagePartContainerPrivate::body() const
+{
+    return const_cast<QMailMessagePartContainerPrivate*>(this)->_body;
+}
+
+void QMailMessagePartContainerPrivate::setBody(const QMailMessageBody& body)
+{
+    // Set the body's properties into our header
+    setBodyProperties(body.contentType(), body.transferEncoding());
+
+    // Multipart messages do not have their own bodies
+    if (body.contentType().type().toLower() != "multipart") {
+        _body = body;
+        _hasBody = !_body.isEmpty();
+    }
+}
+
+void QMailMessagePartContainerPrivate::setBodyProperties(const QMailMessageContentType &type, QMailMessageBody::TransferEncoding encoding)
+{
+    updateHeaderField(type.id(), type.toString(false, false));
+
+    QByteArray encodingName(nameForEncoding(encoding));
+    if (!encodingName.isEmpty()) {
+        updateHeaderField("Content-Transfer-Encoding", encodingName);
+    }
+
+    setDirty();
+}
+
+bool QMailMessagePartContainerPrivate::hasBody() const
+{
+    return _hasBody;
+}
+
+static QByteArray plainId(const QByteArray &id)
+{
+    QByteArray name(id.trimmed());
+    if (name.endsWith(':'))
+        name.chop(1);
+    return name.trimmed();
+}
+
+QByteArray QMailMessagePartContainerPrivate::headerField( const QByteArray &name ) const
+{
+    QList<QByteArray> result = headerFields(name, 1);
+    if (result.count())
+        return result[0];
+
+    return QByteArray();
+}
+
+QList<QByteArray> QMailMessagePartContainerPrivate::headerFields( const QByteArray &name, int maximum ) const
+{
+    QList<QByteArray> result;
+
+    QByteArray id(plainId(name));
+
+    foreach (const QByteArray* field, _header.fieldList()) {
+        QMailMessageHeaderField headerField(*field, QMailMessageHeaderField::UnstructuredField);
+        if (insensitiveEqual(headerField.id(), id)) {
+            result.append(headerField.content());
+            if (maximum > 0 && result.count() == maximum)
+                break;
+        }
+    }
+
+    return result;
+}
+
+QList<QByteArray> QMailMessagePartContainerPrivate::headerFields() const
+{
+    QList<QByteArray> result;
+
+    foreach (const QByteArray* field, _header.fieldList())
+        result.append(*field);
+
+    return result;
+}
+
+void QMailMessagePartContainerPrivate::updateHeaderField(const QByteArray &id, const QByteArray &content)
+{
+    _header.update(id, content);
+    setDirty();
+
+    if (insensitiveEqual(plainId(id), "Content-Type"))
+    {
+        // Extract the stored parts from the supplied field
+        QMailMessageContentType type(content);
+        _multipartType = QMailMessagePartContainer::multipartTypeForName(type.content());
+        _boundary = type.boundary();
+    }
+}
+
+static QByteArray encodedContent(const QByteArray& id, const QString& content)
+{
+    // TODO: Potentially, we should disallow encoding here based on the specific header field
+    bool permitEncoding(true);
+    //QByteArray name(fieldId(id));
+    Q_UNUSED(id)
+
+    return (permitEncoding ? QMailMessageHeaderField::encodeContent(content) : to7BitAscii(content));
+}
+
+void QMailMessagePartContainerPrivate::updateHeaderField(const QByteArray &id, const QString &content)
+{
+    updateHeaderField(id, encodedContent(id, content));
+}
+
+void QMailMessagePartContainerPrivate::appendHeaderField(const QByteArray &id, const QByteArray &content)
+{
+    _header.append( id, content );
+    setDirty();
+
+    if (insensitiveEqual(plainId(id), "Content-Type"))
+    {
+        // Extract the stored parts from the supplied field
+        QMailMessageContentType type(content);
+        _multipartType = QMailMessagePartContainer::multipartTypeForName(type.content());
+        _boundary = type.boundary();
+    }
+}
+
+void QMailMessagePartContainerPrivate::appendHeaderField(const QByteArray &id, const QString &content)
+{
+    appendHeaderField(id, encodedContent(id, content));
+}
+
+void QMailMessagePartContainerPrivate::removeHeaderField(const QByteArray &id)
+{
+    _header.remove(id);
+    setDirty();
+
+    if (insensitiveEqual(plainId(id), "Content-Type"))
+    {
+        // Extract the stored parts from the supplied field
+        _multipartType = QMailMessagePartContainer::MultipartNone;
+        _boundary = QByteArray();
+    }
+}
+
+void QMailMessagePartContainerPrivate::appendPart(const QMailMessagePart &part)
+{
+    QList<QMailMessagePart>::iterator it = _messageParts.insert( _messageParts.end(), part );
+
+    QList<uint> location(_indices);
+    location.append(_messageParts.count());
+    (*it).impl<QMailMessagePartContainerPrivate>()->setLocation(_messageId, location);
+
+    setDirty();
+}
+
+void QMailMessagePartContainerPrivate::prependPart(const QMailMessagePart &part)
+{
+    // Increment the part numbers for existing parts
+    QList<QMailMessagePart>::iterator it = _messageParts.begin(), end = _messageParts.end();
+    for (uint i = 1; it != end; ++it, ++i) {
+        QList<uint> location(_indices);
+        location.append(i + 1);
+        (*it).impl<QMailMessagePartContainerPrivate>()->setLocation(_messageId, location);
+    }
+
+    it = _messageParts.insert( _messageParts.begin(), part );
+
+    QList<uint> location(_indices);
+    location.append(1);
+    (*it).impl<QMailMessagePartContainerPrivate>()->setLocation(_messageId, location);
+
+    setDirty();
+}
+
+void QMailMessagePartContainerPrivate::removePartAt(uint pos)
+{
+    _messageParts.removeAt(pos);
+    setDirty();
+}
+
+void QMailMessagePartContainerPrivate::clear()
+{
+    if (!_messageParts.isEmpty()) {
+        _messageParts.clear();
+        setDirty();
+    }
+}
+
+QMailMessageContentType QMailMessagePartContainerPrivate::contentType() const
+{
+    return QMailMessageContentType(headerField("Content-Type"));
+}
+
+QMailMessageBody::TransferEncoding QMailMessagePartContainerPrivate::transferEncoding() const
+{
+    return encodingForName(headerField("Content-Transfer-Encoding"));
+}
+
+void QMailMessagePartContainerPrivate::parseMimeSinglePart(const QMailMessageHeader& partHeader, LongString body)
+{
+    // Create a part to contain this data
+    QMailMessagePart part;
+    part.setHeader(partHeader, this);
+
+    QMailMessageContentType contentType(part.headerField("Content-Type"));
+    QMailMessageBody::TransferEncoding encoding = encodingForName(part.headerFieldText("Content-Transfer-Encoding").toLatin1());
+    if ( encoding == QMailMessageBody::NoEncoding )
+        encoding = QMailMessageBody::SevenBit;
+
+    if ( contentType.type() == "message" ) { // No tr
+        // TODO: We can't currently handle these types
+    }
+
+    part.setBody(QMailMessageBody::fromLongString(body, contentType, encoding, QMailMessageBody::AlreadyEncoded));
+
+    appendPart(part);
+}
+
+void QMailMessagePartContainerPrivate::parseMimeMultipart(const QMailMessageHeader& partHeader, LongString body, bool insertIntoSelf)
+{
+    static const QByteArray newLine(QMailMessage::CRLF);
+    static const QByteArray marker("--");
+
+    QMailMessagePart part;
+    QMailMessageContentType contentType;
+    QByteArray boundary;
+    QMailMessagePartContainerPrivate* multipartContainer = 0;
+
+    if (insertIntoSelf) {
+        // Insert the parts into ourself
+        multipartContainer = this;
+        contentType = QMailMessageContentType(headerField("Content-Type"));
+        boundary = _boundary;
+    } else {
+        // This object already contains part(s) - use a new part to contain the parts
+        multipartContainer = privatePointer(part);
+
+        // Parse the header fields, and update the part
+        part.setHeader(partHeader, this);
+        contentType = QMailMessageContentType(part.headerField("Content-Type"));
+        boundary = contentType.boundary();
+    }
+
+    // Separate the body into parts delimited by the boundary, and parse them individually
+    QByteArray partDelimiter = marker + boundary;
+    QByteArray partTerminator = newLine + partDelimiter + marker;
+
+    int startPos = body.indexOf(partDelimiter, 0);
+    if (startPos != -1)
+        startPos += partDelimiter.length();
+
+    // Subsequent delimiters include the leading newline
+    partDelimiter.prepend(newLine);
+
+    int endPos = body.indexOf(partTerminator, 0);
+    while ((startPos != -1) && (startPos < endPos))
+    {
+        // Skip the boundary line
+        startPos = body.indexOf(newLine, startPos);
+
+        if ((startPos != -1) && (startPos < endPos))
+        {
+            // Parse the section up to the next boundary marker
+            int nextPos = body.indexOf(partDelimiter, startPos);
+            multipartContainer->parseMimePart(body.mid(startPos, (nextPos - startPos)));
+
+            // Try the next part
+            startPos = nextPos + partDelimiter.length();
+        }
+    }
+
+    if (part.partCount() > 0) {
+        appendPart(part);
+    }
+}
+
+bool QMailMessagePartContainerPrivate::parseMimePart(LongString body)
+{
+    static const QByteArray delimiter((QByteArray(QMailMessage::CRLF) + QMailMessage::CRLF));
+
+    int startPos = 0;
+    int endPos = body.indexOf(delimiter);
+
+    if (startPos <= endPos) {
+        // startPos is the offset of the header, endPos of the delimiter preceding the body
+        QByteArray header(body.mid(startPos, endPos - startPos).toQByteArray());
+
+        // Bypass the delimiter
+        LongString remainder = body.mid(endPos + delimiter.length());
+
+        QMailMessageHeader partHeader = QMailMessageHeader(header);
+        QMailMessageContentType contentType(partHeader.field("Content-Type"));
+
+        // If the content is not available, treat the part as simple
+        if (insensitiveEqual(contentType.type(), "multipart") && !remainder.isEmpty()) {
+            // Parse the body as a multi-part
+            parseMimeMultipart(partHeader, remainder, false);
+        } else {
+            // Parse the remainder as a single part
+            parseMimeSinglePart(partHeader, remainder);
+        }
+        return true;
+    }
+
+    return false;
+}
+
+bool QMailMessagePartContainerPrivate::dirty(bool recursive) const
+{
+    if (_dirty)
+        return true;
+
+    if (recursive) {
+        foreach (const QMailMessagePart& part, _messageParts)
+            if (part.impl<const QMailMessagePartContainerPrivate>()->dirty(true))
+                return true;
+    }
+
+    return false;
+}
+
+void QMailMessagePartContainerPrivate::setDirty(bool value, bool recursive)
+{
+    _dirty = value;
+
+    if (recursive) {
+        const QList<QMailMessagePart>::Iterator end = _messageParts.end();
+        for (QList<QMailMessagePart>::Iterator it = _messageParts.begin(); it != end; ++it)
+            (*it).impl<QMailMessagePartContainerPrivate>()->setDirty(value, true);
+    }
+}
+
+template <typename Stream> 
+void QMailMessagePartContainerPrivate::serialize(Stream &stream) const
+{
+    stream << _multipartType;
+    stream << _messageParts;
+    stream << _boundary;
+    stream << _header;
+    stream << _messageId;
+    stream << _indices;
+    stream << _hasBody;
+    if (_hasBody)
+        stream << _body;
+    stream << _dirty;
+}
+
+template <typename Stream> 
+void QMailMessagePartContainerPrivate::deserialize(Stream &stream)
+{
+    stream >> _multipartType;
+    stream >> _messageParts;
+    stream >> _boundary;
+    stream >> _header;
+    stream >> _messageId;
+    stream >> _indices;
+    stream >> _hasBody;
+    if (_hasBody)
+        stream >> _body;
+    stream >> _dirty;
+}
+
+
+/*!
+    \class QMailMessagePartContainer
+
+    \brief The QMailMessagePartContainer class provides access to a collection of message parts.
+    
+    \ingroup messaginglibrary
+   
+    Message formats such as email messages conforming to 
+    \l{http://www.ietf.org/rfc/rfc2822.txt} {RFC 2822} (Internet Message Format) can consist of 
+    multiple independent parts, whose relationship to each other is defined by the message that 
+    contains those parts.  The QMailMessagePartContainer class provides storage for these related 
+    message parts, and the interface through which they are accessed.
+
+    The multipartType() function returns a member of the MultipartType enumeration, which 
+    describes the relationship of the parts in the container to each other.
+
+    The part container can instead contain a message body element.  In this case, it cannot contain
+    sub-parts, and the multipartType() function will return MultipartType::MultipartNone for the part. 
+    The body element can be accessed via the body() function.
+
+    The QMailMessagePart class is itself derived from QMailMessagePartContainer, which allows
+    messages to support the nesting of part collections within other part collections.
+
+    \sa QMailMessagePart, QMailMessage, QMailMessageBody
+*/    
+
+/*!
+    \typedef QMailMessagePartContainer::ImplementationType
+    \internal
+*/
+
+/*!
+    \fn QMailMessagePartContainer::QMailMessagePartContainer(Subclass*)
+
+    Constructs an empty part container object, in the space allocated 
+    within the subclass instance at \a p.
+*/
+template<typename Subclass>
+QMailMessagePartContainer::QMailMessagePartContainer(Subclass* p)
+    : QPrivatelyImplemented<QMailMessagePartContainerPrivate>(p)
+{
+}
+
+/*! \internal */
+void QMailMessagePartContainer::setHeader(const QMailMessageHeader& partHeader, const QMailMessagePartContainerPrivate* parent)
+{
+    impl(this)->setHeader(partHeader, parent);
+}
+
+/*!
+    Returns the number of attachments the message has.
+*/  
+uint QMailMessagePartContainer::partCount() const
+{
+    return impl(this)->_messageParts.count();
+}
+
+/*!
+    Append \a part to the list of attachments for the message.
+*/
+void QMailMessagePartContainer::appendPart(const QMailMessagePart &part)
+{
+    impl(this)->appendPart(part);
+}
+
+/*!
+    Prepend \a part to the list of attachments for the message.
+*/
+void QMailMessagePartContainer::prependPart(const QMailMessagePart &part)
+{
+    impl(this)->prependPart(part);
+}
+
+/*!
+    Removes the part at the index \a pos.
+
+    \a pos must be a valid index position in the list (i.e., 0 <= i < partCount()).
+*/
+void QMailMessagePartContainer::removePartAt(uint pos)
+{
+    impl(this)->removePartAt(pos);
+}
+
+/*!
+    Returns a const reference to the item at position \a pos in the list of 
+    attachments for the message.
+
+    \a pos must be a valid index position in the list (i.e., 0 <= i < partCount()).
+*/
+const QMailMessagePart& QMailMessagePartContainer::partAt(uint pos) const
+{
+    return impl(this)->_messageParts[pos];
+}
+
+/*!
+    Returns a non-const reference to the item at position \a pos in the list of 
+    attachments for the message.
+
+    \a pos must be a valid index position in the list (i.e., 0 <= i < partCount()).
+*/
+QMailMessagePart& QMailMessagePartContainer::partAt(uint pos)
+{
+    return impl(this)->_messageParts[pos];
+}
+
+/*!
+    Clears the list of attachments associated with the message.
+*/
+void QMailMessagePartContainer::clearParts()
+{
+    impl(this)->clear();
+}
+
+/*!
+    Returns the type of multipart relationship shared by the parts contained within this container, or
+    \l {QMailMessagePartContainerFwd::MultipartNone}{MultipartNone} if the content is not a multipart message.
+*/  
+QMailMessagePartContainer::MultipartType QMailMessagePartContainer::multipartType() const
+{
+    return impl(this)->_multipartType;
+}
+
+/*!
+    Sets the multipart state of the message to \a type.
+*/
+void QMailMessagePartContainer::setMultipartType(QMailMessagePartContainer::MultipartType type)
+{
+    impl(this)->setMultipartType(type);
+}
+
+/*!
+    Returns the boundary text used to delimit the container's parts when encoded in RFC 2822 form.
+*/
+QByteArray QMailMessagePartContainer::boundary() const
+{
+    return impl(this)->boundary();
+}
+
+/*!
+    Sets the boundary text used to delimit the container's parts when encoded in RFC 2822 form to \a text.
+*/
+void QMailMessagePartContainer::setBoundary(const QByteArray& text)
+{
+    impl(this)->setBoundary(text);
+}
+
+/*!
+    Sets the part to contain the body element \a body.
+*/
+void QMailMessagePartContainer::setBody(const QMailMessageBody& body)
+{
+    impl(this)->setBody(body);
+}
+
+/*!
+    Returns the body element contained by the part.
+*/
+QMailMessageBody QMailMessagePartContainer::body() const
+{
+    return impl(this)->body();
+}
+
+/*!
+    Returns true if the part contains a body element; otherwise returns false.
+*/
+bool QMailMessagePartContainer::hasBody() const
+{
+    return impl(this)->hasBody();
+}
+
+/*!
+    Returns the content type of this part.  Where hasBody() is true, the type of the
+    contained body element is returned; otherwise a content type matching the 
+    multipartType() for this part is returned.
+
+    \sa hasBody(), QMailMessageBody::contentType(), multipartType()
+*/
+QMailMessageContentType QMailMessagePartContainer::contentType() const
+{
+    return impl(this)->contentType();
+}
+
+/*!
+    Returns the transfer encoding type of this part.  Where hasBody() is true, the
+    transfer encoding type of the contained body element is returned; otherwise, the
+    transfer encoding type specified by the 'Content-Transfer-Encoding' field of the 
+    header for this part is returned.
+
+    \sa hasBody(), QMailMessageBody::transferEncoding()
+*/
+QMailMessageBody::TransferEncoding QMailMessagePartContainer::transferEncoding() const
+{
+    return impl(this)->transferEncoding();
+}
+
+/*!
+    Returns the text of the first header field with the given \a id.
+*/
+QString QMailMessagePartContainer::headerFieldText( const QString &id ) const
+{
+    return impl(this)->headerFieldText(id);
+}
+
+/*!
+    Returns an object containing the value of the first header field with the given \a id.
+    If \a fieldType is QMailMessageHeaderField::StructuredField, then the field content 
+    will be parsed assuming a format equivalent to that used for the RFC 2045 'Content-Type' 
+    and RFC 2183 'Content-Disposition' header fields.
+*/
+QMailMessageHeaderField QMailMessagePartContainer::headerField( const QString &id, QMailMessageHeaderField::FieldType fieldType ) const
+{
+    QByteArray plainId( to7BitAscii(id) );
+    const QByteArray& content = impl(this)->headerField( plainId );
+    if ( !content.isEmpty() )
+        return QMailMessageHeaderField( plainId, content, fieldType );
+
+    return QMailMessageHeaderField();
+}
+
+/*!
+    Returns a list containing the text of each header field with the given \a id.
+*/
+QStringList QMailMessagePartContainer::headerFieldsText( const QString &id ) const
+{
+    QStringList result;
+
+    foreach (const QByteArray& item, impl(this)->headerFields( to7BitAscii(id) ))
+        result.append(decodedContent( id, item ));
+
+    return result;
+}
+
+/*!
+    Returns a list of objects containing the value of each header field with the given \a id.
+    If \a fieldType is QMailMessageHeaderField::StructuredField, then the field content will 
+    be parsed assuming a format equivalent to that used for the RFC 2045 'Content-Type' and 
+    RFC 2183 'Content-Disposition' header fields.
+*/
+QList<QMailMessageHeaderField> QMailMessagePartContainer::headerFields( const QString &id, QMailMessageHeaderField::FieldType fieldType ) const
+{
+    QList<QMailMessageHeaderField> result;
+
+    QByteArray plainId( to7BitAscii(id) );
+    foreach (const QByteArray& content, impl(this)->headerFields( plainId ))
+        result.append( QMailMessageHeaderField( plainId, content, fieldType ) );
+
+    return result;
+}
+
+/*!
+    Returns a list of objects containing the value of each header field contained by the part.
+    Header field objects returned by this function are not 'structured'.
+*/
+QList<QMailMessageHeaderField> QMailMessagePartContainer::headerFields() const
+{
+    QList<QMailMessageHeaderField> result;
+
+    foreach (const QByteArray& content, impl(this)->headerFields())
+        result.append( QMailMessageHeaderField( content, QMailMessageHeaderField::UnstructuredField) );
+
+    return result;
+}
+
+/*!
+    Sets the value of the first header field with identity \a id to \a value if it already 
+    exists; otherwise adds the header with the supplied id and value.  If \a value is of 
+    the form "<id>:<content>", then only the part after the semi-colon is processed.
+
+    RFC 2822 encoding requires header fields to be transmitted in ASCII characters.  
+    If \a value contains non-ASCII characters, it will be encoded to ASCII via the 
+    QMailMessageHeaderField::encodeContent() function; depending on the specific header 
+    field this may result in illegal content.  Where possible, clients should encode 
+    non-ASCII data prior to calling setHeaderField.
+
+    \sa QMailMessageHeaderField
+*/
+void QMailMessagePartContainer::setHeaderField( const QString& id, const QString& value )
+{
+    QByteArray plainId( to7BitAscii(id) );
+
+    int index = value.indexOf(':');
+    if (index != -1 ) {
+        // Is the header field id replicated in the value?
+        QString prefix(value.left(index));
+        if ( insensitiveEqual( to7BitAscii(prefix.trimmed()), plainId.trimmed() ) ) {
+            impl(this)->updateHeaderField( plainId, value.mid(index + 1) );
+            return;
+        }
+    }
+
+    impl(this)->updateHeaderField( plainId, value );
+}
+
+/*!
+    Sets the first header field with identity matching \a field to have the content of
+    \a field.
+*/
+void QMailMessagePartContainer::setHeaderField( const QMailMessageHeaderField& field )
+{
+    impl(this)->updateHeaderField( field.id(), field.toString(false, false) );
+}
+
+/*!
+    Appends a new header field with id \a id and value \a value to the existing
+    list of header fields.  Any existing header fields with the same id are not modified.
+    If \a value is of the form "<id>:<content>", then only the part after the 
+    semi-colon is processed.
+
+    RFC 2822 encoding requires header fields to be transmitted in ASCII characters.  
+    If \a value contains non-ASCII characters, it will be encoded to ASCII via the 
+    QMailMessageHeaderField::encodeContent() function; depending on the specific header 
+    field this may result in illegal content.  Where possible, clients should encode 
+    non-ASCII data prior to calling appendHeaderField.
+
+    \sa QMailMessageHeaderField
+*/
+void QMailMessagePartContainer::appendHeaderField( const QString& id, const QString& value )
+{
+    QByteArray plainId( to7BitAscii(id) );
+
+    int index = value.indexOf(':');
+    if (index != -1 ) {
+        // Is the header field id replicated in the value?
+        QString prefix(value.left(index));
+        if ( insensitiveEqual( to7BitAscii(prefix.trimmed()), plainId.trimmed() ) ) {
+            impl(this)->appendHeaderField( plainId, value.mid(index + 1) );
+            return;
+        }
+    }
+
+    impl(this)->appendHeaderField( plainId, value );
+}
+
+/*!
+    Appends a new header field with the properties of \a field.  Any existing header 
+    fields with the same id are not modified.
+*/
+void QMailMessagePartContainer::appendHeaderField( const QMailMessageHeaderField& field )
+{
+    impl(this)->appendHeaderField( field.id(), field.toString(false, false) );
+}
+
+/*!
+    Removes all existing header fields with id equal to \a id.
+*/
+void QMailMessagePartContainer::removeHeaderField( const QString& id )
+{
+    impl(this)->removeHeaderField( to7BitAscii(id) );
+}
+
+/*!
+    Returns the multipart type that corresponds to the type name \a name.
+*/
+QMailMessagePartContainer::MultipartType QMailMessagePartContainer::multipartTypeForName(const QByteArray &name)
+{
+    QByteArray ciName = name.toLower();
+
+    if ((ciName == "multipart/signed") || (ciName == "signed"))
+        return QMailMessagePartContainer::MultipartSigned;
+
+    if ((ciName == "multipart/encrypted") || (ciName == "encrypted"))
+        return QMailMessagePartContainer::MultipartEncrypted;
+
+    if ((ciName == "multipart/mixed") || (ciName == "mixed"))
+        return QMailMessagePartContainer::MultipartMixed;
+
+    if ((ciName == "multipart/alternative") || (ciName == "alternative"))
+        return QMailMessagePartContainer::MultipartAlternative;
+
+    if ((ciName == "multipart/digest") || (ciName == "digest"))
+        return QMailMessagePartContainer::MultipartDigest;
+
+    if ((ciName == "multipart/parallel") || (ciName == "parallel"))
+        return QMailMessagePartContainer::MultipartParallel;
+
+    if ((ciName == "multipart/related") || (ciName == "related"))
+        return QMailMessagePartContainer::MultipartRelated;
+
+    if ((ciName == "multipart/form") || (ciName == "form"))
+        return QMailMessagePartContainer::MultipartFormData;
+
+    if ((ciName == "multipart/report") || (ciName == "report"))
+        return QMailMessagePartContainer::MultipartReport;
+
+    return QMailMessagePartContainer::MultipartNone;
+}
+
+/*!
+    Returns the standard textual representation for the multipart type \a type.
+*/
+QByteArray QMailMessagePartContainer::nameForMultipartType(QMailMessagePartContainer::MultipartType type)
+{
+    switch (type) 
+    {
+        case QMailMessagePartContainer::MultipartSigned:
+        {
+            return "multipart/signed";
+        }
+        case QMailMessagePartContainer::MultipartEncrypted:
+        {
+            return "multipart/encrypted";
+        }
+        case QMailMessagePartContainer::MultipartMixed:
+        {
+            return "multipart/mixed";
+        }
+        case QMailMessagePartContainer::MultipartAlternative:
+        {
+            return "multipart/alternative";
+        }
+        case QMailMessagePartContainer::MultipartDigest:
+        {
+            return "multipart/digest";
+        }
+        case QMailMessagePartContainer::MultipartParallel:
+        {
+            return "multipart/parallel";
+        }
+        case QMailMessagePartContainer::MultipartRelated:
+        {
+            return "multipart/related";
+        }
+        case QMailMessagePartContainer::MultipartFormData:
+        {
+            return "multipart/form-data";
+        }
+        case QMailMessagePartContainer::MultipartReport:
+        {
+            return "multipart/report";
+        }
+        case QMailMessagePartContainer::MultipartNone:
+            break;
+    }
+
+    return QByteArray();
+}
+
+/*! \internal */
+uint QMailMessagePartContainer::indicativeSize() const
+{
+    return impl(this)->indicativeSize();
+}
+
+struct DummyChunkProcessor
+{
+    void operator()(QMailMessage::ChunkType) {}
+};
+
+/*! \internal */
+void QMailMessagePartContainer::outputParts(QDataStream& out, bool addMimePreamble, bool includeAttachments, bool excludeInternalFields) const
+{
+    QDataStream* ds(&out);
+    impl(this)->outputParts<DummyChunkProcessor>(&ds, addMimePreamble, includeAttachments, excludeInternalFields, 0);
+}
+
+/*! \internal */
+void QMailMessagePartContainer::outputBody( QDataStream& out, bool includeAttachments ) const
+{
+    impl(this)->outputBody( out, includeAttachments );
+}
+
+/*!
+    \fn QMailMessagePartContainer::contentAvailable() const
+
+    Returns true if the entire content of this element is available; otherwise returns false.
+*/
+
+/*!
+    \fn QMailMessagePartContainer::partialContentAvailable() const
+
+    Returns true if some portion of the content of this element is available; otherwise returns false.
+*/
+
+
+/* QMailMessagePart */
+
+QMailMessagePartPrivate::QMailMessagePartPrivate()
+    : QMailMessagePartContainerPrivate(this)
+{
+}
+
+QMailMessagePart::ReferenceType QMailMessagePartPrivate::referenceType() const
+{
+    if (_referenceId.isValid())
+        return QMailMessagePart::MessageReference;
+        
+    if (_referenceLocation.isValid())
+        return QMailMessagePart::PartReference;
+
+    return QMailMessagePart::None;
+}
+
+QMailMessageId QMailMessagePartPrivate::messageReference() const
+{
+    return _referenceId;
+}
+
+QMailMessagePart::Location QMailMessagePartPrivate::partReference() const
+{
+    return _referenceLocation;
+}
+
+QString QMailMessagePartPrivate::referenceResolution() const
+{
+    return _resolution;
+}
+
+void QMailMessagePartPrivate::setReferenceResolution(const QString &uri)
+{
+    _resolution = uri;
+}
+
+bool QMailMessagePartPrivate::contentAvailable() const
+{
+    if (_multipartType != QMailMessage::MultipartNone)
+        return true;
+
+    if (_body.isEmpty())
+        return false;
+
+    // Complete content is available only if the 'partial-content' header field is not present
+    QByteArray fieldName(internalPrefix() + "partial-content");
+    return (headerField(fieldName).isEmpty());
+}
+
+bool QMailMessagePartPrivate::partialContentAvailable() const
+{
+    return ((_multipartType != QMailMessage::MultipartNone) || !_body.isEmpty());
+}
+
+template <typename F>
+void QMailMessagePartPrivate::output(QDataStream **out, bool addMimePreamble, bool includeAttachments, bool excludeInternalFields, F *func) const
+{
+    static const DataString newLine('\n');
+
+    _header.output( **out, QList<QByteArray>(), excludeInternalFields );
+    **out << DataString('\n');
+
+    QMailMessagePart::ReferenceType type(referenceType());
+    if (type == QMailMessagePart::None) {
+        if ( hasBody() ) {
+            outputBody( **out, includeAttachments );
+        } else {
+            outputParts<F>( out, addMimePreamble, includeAttachments, excludeInternalFields, func );
+        }
+    } else {
+        if (includeAttachments) {
+            // The next part must be a different chunk
+            if (func) {
+                (*func)(QMailMessage::Text);
+            }
+
+            if (!_resolution.isEmpty()) {
+                **out << DataString(_resolution.toAscii());
+            } else {
+                qWarning() << "QMailMessagePartPrivate::output - unresolved reference part!";
+            }
+
+            if (func) {
+                (*func)(QMailMessage::Reference);
+            }
+        }
+    }
+}
+
+template <typename Stream> 
+void QMailMessagePartPrivate::serialize(Stream &stream) const
+{
+    QMailMessagePartContainerPrivate::serialize(stream);
+
+    stream << _referenceId;
+    stream << _referenceLocation;
+    stream << _resolution;
+}
+
+template <typename Stream> 
+void QMailMessagePartPrivate::deserialize(Stream &stream)
+{
+    QMailMessagePartContainerPrivate::deserialize(stream);
+
+    stream >> _referenceId;
+    stream >> _referenceLocation;
+    stream >> _resolution;
+}
+
+void QMailMessagePartPrivate::setReference(const QMailMessageId &id, 
+                                           const QMailMessageContentType& type, 
+                                           QMailMessageBody::TransferEncoding encoding)
+{
+    _referenceId = id;
+    setBodyProperties(type, encoding);
+}
+
+void QMailMessagePartPrivate::setReference(const QMailMessagePart::Location &location, 
+                                           const QMailMessageContentType& type, 
+                                           QMailMessageBody::TransferEncoding encoding)
+{
+    _referenceLocation = location;
+    setBodyProperties(type, encoding);
+}
+
+bool QMailMessagePartPrivate::contentModified() const
+{
+    // Specific to this part
+    return dirty(false);
+}
+
+void QMailMessagePartPrivate::setUnmodified()
+{
+    setDirty(false, false);
+}
+
+QMailMessagePartContainerPrivate* QMailMessagePartContainerPrivate::privatePointer(QMailMessagePart& part)
+{
+    /* Nasty workaround required to access this data without detaching a copy... */
+    return const_cast<QMailMessagePartPrivate*>(static_cast<const QMailMessagePartPrivate*>(part.d.constData()));
+}
+
+/*!
+    \fn bool QMailMessagePartContainer::foreachPart(F func)
+    
+    Applies the function or functor \a func to each part contained within the container.
+    \a func must implement the signature 'bool operator()(QMailMessagePart &)', and must
+    return true to indicate success, or false to end the traversal operation.
+
+    Returns true if all parts of the message were traversed, and \a func returned true for
+    every invocation; else returns false.
+*/
+
+/*!
+    \fn bool QMailMessagePartContainer::foreachPart(F func) const
+    
+    Applies the function or functor \a func to each part contained within the container.
+    \a func must implement the signature 'bool operator()(const QMailMessagePart &)', and must
+    return true to indicate success, or false to end the traversal operation.
+
+    Returns true if all parts of the message were traversed, and \a func returned true for
+    every invocation; else returns false.
+*/
+
+
+//===========================================================================
+/*      Mail Message Part   */
+
+/*!
+    \class QMailMessagePart
+
+    \brief The QMailMessagePart class provides a convenient interface for working 
+    with message attachments.
+
+    \ingroup messaginglibrary
+    
+    A message part inherits the properties of QMailMessagePartContainer, and can 
+    therefore contain a message body or a collection of sub-parts.  
+    
+    A message part differs from a message proper in that a part will often have 
+    properties specified by the MIME multipart specification, not relevant to 
+    messages.  These include the 'name' and 'filename' parameters of the Content-Type 
+    and Content-Disposition fields, and the Content-Id and Content-Location fields.
+
+    A message part may consist entirely of a reference to an external message, or
+    a part within an external message.  Parts that consists of references may be 
+    used with some protocols that permit data to be transmitted by reference, such
+    as IMAP with the URLAUTH extension.  Not all messaging protocols support the
+    use of content references. The partReference() and messageReference() functions
+    enable the creation of reference parts.
+
+    \sa QMailMessagePartContainer
+*/
+
+/*!
+    \typedef QMailMessagePart::ImplementationType
+    \internal
+*/
+
+
+/*!
+    \class QMailMessagePart::Location
+
+    \brief The Location class contains a specification of the location of a message part
+    with the message that contains it.
+
+    \ingroup messaginglibrary
+    
+    A Location object is used to refer to a single part within a multi-part message.  The
+    location can be used to reference a part within a QMailMessage object, via the 
+    \l{QMailMessage::partAt()}{partAt} function.
+*/
+
+/*!
+    Creates an empty part location object.
+*/
+QMailMessagePart::Location::Location()
+    : d(new QMailMessagePart::LocationPrivate)
+{
+}
+
+/*!
+    Creates a part location object referring to the location given by \a description.
+
+    \sa toString()
+*/
+QMailMessagePart::Location::Location(const QString& description)
+    : d(new QMailMessagePart::LocationPrivate)
+{
+    QString indices;
+
+    int separator = description.indexOf('-');
+    if (separator != -1) {
+        d->_messageId = QMailMessageId(description.left(separator).toULongLong());
+        indices = description.mid(separator + 1);
+    } else {
+        indices = description;
+    }
+
+    if (!indices.isEmpty()) {
+        foreach (const QString &index, indices.split(".")) {
+            d->_indices.append(index.toUInt());
+        }
+    }
+
+    Q_ASSERT(description == toString(separator == -1 ? false : true));
+}
+
+/*!
+    Creates a part location object containing a copy of \a other.
+*/
+QMailMessagePart::Location::Location(const Location& other)
+    : d(new QMailMessagePart::LocationPrivate)
+{
+    *this = other;
+}
+
+/*!
+    Creates a location object containing the location of \a part.
+*/
+QMailMessagePart::Location::Location(const QMailMessagePart& part)
+    : d(new QMailMessagePart::LocationPrivate)
+{
+    const QMailMessagePartContainerPrivate* partImpl = part.impl<const QMailMessagePartContainerPrivate>();
+
+    d->_messageId = partImpl->_messageId;
+    d->_indices = partImpl->_indices;
+}
+
+/*! \internal */
+QMailMessagePart::Location::~Location()
+{
+    delete d;
+}
+
+/*! \internal */
+const QMailMessagePart::Location &QMailMessagePart::Location::operator=(const QMailMessagePart::Location &other)
+{
+    d->_messageId = other.d->_messageId;
+    d->_indices = other.d->_indices;
+
+    return *this;
+}
+
+/*! 
+    Returns true if the location object contains the location of a valid message part.
+    If \a extended is true, the location must also contain a valid message identifier.
+*/
+bool QMailMessagePart::Location::isValid(bool extended) const
+{
+    return ((!extended || d->_messageId.isValid()) && !d->_indices.isEmpty());
+}
+
+/*! 
+    Returns the identifier of the message that contains the part with this location.
+*/
+QMailMessageId QMailMessagePart::Location::containingMessageId() const
+{
+    return d->_messageId;
+}
+
+/*! 
+    Sets the identifier of the message that contains the part with this location to \a id.
+*/
+void QMailMessagePart::Location::setContainingMessageId(const QMailMessageId &id)
+{
+    d->_messageId = id;
+}
+
+/*!
+    Returns a textual representation of the part location.
+    If \a extended is true, the representation contains the identifier of the containing message.
+*/
+QString QMailMessagePart::Location::toString(bool extended) const
+{
+    QString result;
+    if (extended)
+        result = QString::number(d->_messageId.toULongLong()) + '-';
+
+    QStringList numbers;
+    foreach (uint index, d->_indices)
+        numbers.append(QString::number(index));
+
+    return result.append(numbers.join("."));
+}
+
+/*! 
+    \fn QMailMessagePart::Location::serialize(Stream&) const
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessagePart::Location::serialize(Stream &stream) const
+{
+    stream << d->_messageId;
+    stream << d->_indices;
+}
+
+template void QMailMessagePart::Location::serialize(QDataStream &) const;
+
+/*! 
+    \fn QMailMessagePart::Location::deserialize(Stream&)
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessagePart::Location::deserialize(Stream &stream)
+{
+    stream >> d->_messageId;
+    stream >> d->_indices;
+}
+
+template void QMailMessagePart::Location::deserialize(QDataStream &);
+
+/*!
+    Constructs an empty message part object.
+*/
+QMailMessagePart::QMailMessagePart()
+    : QMailMessagePartContainer(new QMailMessagePartPrivate)
+{
+}
+
+/*!
+   Creates a QMailMessagePart containing an attachment of type \a disposition, from the 
+   data contained in \a filename, of content type \a type and using the transfer encoding
+   \a encoding.  The current status of the data is specified as \a status.
+
+   \sa QMailMessageBody::fromFile()
+*/
+QMailMessagePart QMailMessagePart::fromFile(const QString& filename,
+                                            const QMailMessageContentDisposition& disposition, 
+                                            const QMailMessageContentType& type, 
+                                            QMailMessageBody::TransferEncoding encoding, 
+                                            QMailMessageBody::EncodingStatus status)
+{
+    QMailMessagePart part;
+    part.setBody( QMailMessageBody::fromFile( filename, type, encoding, status ) );
+    part.setContentDisposition( disposition );
+
+    return part;
+}
+
+/*!
+   Creates a QMailMessagePart containing an attachment of type \a disposition, from the 
+   data read from \a in, of content type \a type and using the transfer encoding
+   \a encoding.  The current status of the data is specified as \a status.
+
+   \sa QMailMessageBody::fromStream()
+*/
+QMailMessagePart QMailMessagePart::fromStream(QDataStream& in,
+                                              const QMailMessageContentDisposition& disposition, 
+                                              const QMailMessageContentType& type, 
+                                              QMailMessageBody::TransferEncoding encoding, 
+                                              QMailMessageBody::EncodingStatus status)
+{
+    QMailMessagePart part;
+    part.setBody( QMailMessageBody::fromStream( in, type, encoding, status ) );
+    part.setContentDisposition( disposition );
+
+    return part;
+}
+
+/*!
+   Creates a QMailMessagePart containing an attachment of type \a disposition, from the 
+   data contained in \a input, of content type \a type and using the transfer encoding
+   \a encoding.  The current status of the data is specified as \a status.
+
+   \sa QMailMessageBody::fromData()
+*/
+QMailMessagePart QMailMessagePart::fromData(const QByteArray& input,
+                                            const QMailMessageContentDisposition& disposition, 
+                                            const QMailMessageContentType& type, 
+                                            QMailMessageBody::TransferEncoding encoding, 
+                                            QMailMessageBody::EncodingStatus status)
+{
+    QMailMessagePart part;
+    part.setBody( QMailMessageBody::fromData( input, type, encoding, status ) );
+    part.setContentDisposition( disposition );
+
+    return part;
+}
+
+/*!
+   Creates a QMailMessagePart containing an attachment of type \a disposition, from the 
+   data read from \a in, of content type \a type and using the transfer encoding
+   \a encoding.
+
+   \sa QMailMessageBody::fromStream()
+*/
+QMailMessagePart QMailMessagePart::fromStream(QTextStream& in,
+                                              const QMailMessageContentDisposition& disposition, 
+                                              const QMailMessageContentType& type, 
+                                              QMailMessageBody::TransferEncoding encoding)
+{
+    QMailMessagePart part;
+    part.setBody( QMailMessageBody::fromStream( in, type, encoding ) );
+    part.setContentDisposition( disposition );
+
+    return part;
+}
+
+/*!
+   Creates a QMailMessagePart containing an attachment of type \a disposition, from the 
+   data contained in \a input, of content type \a type and using the transfer encoding
+   \a encoding.
+
+   \sa QMailMessageBody::fromData()
+*/
+QMailMessagePart QMailMessagePart::fromData(const QString& input,
+                                            const QMailMessageContentDisposition& disposition, 
+                                            const QMailMessageContentType& type, 
+                                            QMailMessageBody::TransferEncoding encoding)
+{
+    QMailMessagePart part;
+    part.setBody( QMailMessageBody::fromData( input, type, encoding ) );
+    part.setContentDisposition( disposition );
+
+    return part;
+}
+
+/*!
+    Creates a QMailMessagePart containing an attachment of type \a disposition, whose
+    content is a reference to the message identified by \a messageId.  The resulting 
+    part has content type \a type and uses the transfer encoding \a encoding.
+    
+    The message reference can only be resolved by transmitting the message to an external 
+    server, where both the originating server of the referenced message and the receiving 
+    server of the new message support resolution of the content reference.
+*/
+QMailMessagePart QMailMessagePart::fromMessageReference(const QMailMessageId &messageId,
+                                                        const QMailMessageContentDisposition& disposition, 
+                                                        const QMailMessageContentType& type, 
+                                                        QMailMessageBody::TransferEncoding encoding)
+{
+    QMailMessagePart part;
+    part.setReference(messageId, type, encoding);
+    part.setContentDisposition(disposition);
+
+    return part;
+}
+
+/*!
+    Creates a QMailMessagePart containing an attachment of type \a disposition, whose
+    content is a reference to the message part identified by \a partLocation.  The 
+    resulting part has content type \a type and uses the transfer encoding \a encoding.
+    
+    The part reference can only be resolved by transmitting the message to an external 
+    server, where both the originating server of the referenced part's message and the 
+    receiving server of the new message support resolution of the content reference.
+*/
+QMailMessagePart QMailMessagePart::fromPartReference(const QMailMessagePart::Location &partLocation,
+                                                     const QMailMessageContentDisposition& disposition, 
+                                                     const QMailMessageContentType& type, 
+                                                     QMailMessageBody::TransferEncoding encoding)
+{
+    QMailMessagePart part;
+    part.setReference(partLocation, type, encoding);
+    part.setContentDisposition(disposition);
+
+    return part;
+}
+
+/*!
+    Sets the part content to contain a reference to the message identified by \a id,
+    having content type \a type and using the transfer encoding \a encoding.
+    
+    The message reference can only be resolved by transmitting the message to an external 
+    server, where both the originating server of the referenced message and the receiving 
+    server of the new message support resolution of the content reference.
+
+    \sa referenceType(), setReferenceResolution()
+*/
+void QMailMessagePart::setReference(const QMailMessageId &id, const QMailMessageContentType& type, QMailMessageBody::TransferEncoding encoding)
+{
+    impl(this)->setReference(id, type, encoding);
+}
+
+/*!
+    Sets the part content to contain a reference to the message part identified by \a location,
+    having content type \a type and using the transfer encoding \a encoding.
+    
+    The part reference can only be resolved by transmitting the message to an external 
+    server, where both the originating server of the referenced part's message and the 
+    receiving server of the new message support resolution of the content reference.
+
+    \sa referenceType(), setReferenceResolution()
+*/
+void QMailMessagePart::setReference(const QMailMessagePart::Location &location, const QMailMessageContentType& type, QMailMessageBody::TransferEncoding encoding)
+{
+    impl(this)->setReference(location, type, encoding);
+}
+
+/*!
+    Returns the Content-Id header field for the part, if present; otherwise returns an empty string.
+
+    If the header field content is surrounded by angle brackets, these are removed.
+*/
+QString QMailMessagePart::contentID() const
+{
+    QString result(headerFieldText("Content-ID"));
+    if (!result.isEmpty() && (result[0] == QChar('<')) && (result[result.length() - 1] == QChar('>'))) {
+        return result.mid(1, result.length() - 2);
+    }
+
+    return result;
+}
+
+/*!
+    Sets the Content-Id header field for the part to contain \a id.
+
+    If \a id is not surrounded by angle brackets, these are added.
+*/
+void QMailMessagePart::setContentID(const QString &id)
+{
+    QString str(id);
+    if (!str.isEmpty()) {
+        if (str[0] != QChar('<')) {
+            str.prepend('<');
+        }
+        if (str[str.length() - 1] != QChar('>')) {
+            str.append('>');
+        }
+    }
+
+    setHeaderField("Content-ID", str);
+}
+
+/*!
+    Returns the Content-Location header field for the part, if present; 
+    otherwise returns an empty string.
+*/
+QString QMailMessagePart::contentLocation() const
+{
+    return headerFieldText("Content-Location");
+}
+
+/*!
+    Sets the Content-Location header field for the part to contain \a location.
+*/
+void QMailMessagePart::setContentLocation(const QString &location)
+{
+    setHeaderField("Content-Location", location);
+}
+
+/*!
+    Returns the Content-Description header field for the part, if present; 
+    otherwise returns an empty string.
+*/
+QString QMailMessagePart::contentDescription() const
+{
+    return headerFieldText("Content-Description");
+}
+
+/*!
+    Sets the Content-Description header field for the part to contain \a description.
+*/
+void QMailMessagePart::setContentDescription(const QString &description)
+{
+    setHeaderField("Content-Description", description);
+}
+
+/*!
+    Returns the Content-Disposition header field for the part.
+*/
+QMailMessageContentDisposition QMailMessagePart::contentDisposition() const
+{
+    return QMailMessageContentDisposition(headerField("Content-Disposition"));
+}
+
+/*!
+    Sets the Content-Disposition header field for the part to contain \a disposition.
+*/
+void QMailMessagePart::setContentDisposition(const QMailMessageContentDisposition &disposition)
+{
+    setHeaderField("Content-Disposition", disposition.toString());
+}
+
+/*!
+    Returns the Content-Language header field for the part, if present; 
+    otherwise returns an empty string.
+*/
+QString QMailMessagePart::contentLanguage() const
+{
+    return headerFieldText("Content-Language");
+}
+
+/*!
+    Sets the Content-Language header field for the part to contain \a language.
+*/
+void QMailMessagePart::setContentLanguage(const QString &language)
+{
+    setHeaderField("Content-Language", language);
+}
+
+/*!
+    Returns the number of the part, if it has been set; otherwise returns -1.
+*/
+int QMailMessagePart::partNumber() const
+{
+    return impl(this)->partNumber();
+}
+
+/*!
+    Returns the location of the part within the message.
+*/
+QMailMessagePart::Location QMailMessagePart::location() const
+{
+    return QMailMessagePart::Location(*this);
+}
+
+/*!
+    Returns a non-empty string to identify the part, appropriate for display.  If the part 
+    'Content-Type' header field contains a 'name' parameter, that value is used. Otherwise, 
+    if the part has a 'Content-Disposition' header field containing a 'filename' parameter, 
+    that value is used.  Otherwise, if the part has a 'Content-ID' header field, that value 
+    is used.  Finally, a usable name will be created by combining the content type of the 
+    part with the part's number.
+
+    \sa identifier()
+*/
+QString QMailMessagePart::displayName() const
+{
+    QString id(contentType().name());
+
+    if (id.isEmpty())
+        id = contentDisposition().filename();
+
+    if (id.isEmpty())
+        id = contentID();
+
+    if (id.isEmpty()) {
+        int partNumber = impl(this)->partNumber();
+        if (partNumber != -1) {
+            id = QString::number(partNumber) + " ";
+        }
+        id += contentType().content();
+    }
+
+    return id;
+}
+
+/*!
+    Returns a non-empty string to identify the part, appropriate for storage.  If the part 
+    has a 'Content-ID' header field, that value is used.  Otherwise, if the part has a 
+    'Content-Disposition' header field containing a 'filename' parameter, that value is used.
+    Otherwise, if the part 'Content-Type' header field contains a 'name' parameter, that value 
+    is used.  Finally, the part's number will be returned.
+*/
+QString QMailMessagePart::identifier() const
+{
+    QString id(contentID());
+
+    if (id.isEmpty())
+        id = contentDisposition().filename();
+
+    if (id.isEmpty())
+        id = contentType().name();
+
+    if (id.isEmpty())
+        id = QString::number(impl(this)->partNumber());
+
+    return id;
+}
+
+/*!
+    Returns the type of reference that this message part constitutes.
+
+    \sa setReference()
+*/
+QMailMessagePart::ReferenceType QMailMessagePart::referenceType() const
+{
+    return impl(this)->referenceType();
+}
+
+/*!
+    Returns the identifier of the message that this part references.
+
+    The result will be meaningful only when referenceType() yields
+    \l{QMailMessagePartFwd::MessageReference}{QMailMessagePart::MessageReference}.
+
+    \sa referenceType(), partReference(), referenceResolution()
+*/
+QMailMessageId QMailMessagePart::messageReference() const
+{
+    return impl(this)->messageReference();
+}
+
+/*!
+    Returns the location of the message part that this part references.
+
+    The result will be meaningful only when referenceType() yields
+    \l{QMailMessagePartFwd::PartReference}{QMailMessagePart::PartReference}.
+
+    \sa referenceType(), messageReference(), referenceResolution()
+*/
+QMailMessagePart::Location QMailMessagePart::partReference() const
+{
+    return impl(this)->partReference();
+}
+
+/*!
+    Returns the URI that resolves the reference encoded into this message part.
+
+    The result will be meaningful only when referenceType() yields other than
+    \l{QMailMessagePartFwd::None}{QMailMessagePart::None}.
+
+    \sa setReferenceResolution(), referenceType()
+*/
+QString QMailMessagePart::referenceResolution() const
+{
+    return impl(this)->referenceResolution();
+}
+
+/*!
+    Sets the URI that resolves the reference encoded into this message part to \a uri.
+
+    The reference URI is meaningful only when referenceType() yields other than
+    \l{QMailMessagePartFwd::None}{QMailMessagePart::None}.
+
+    \sa referenceResolution(), referenceType()
+*/
+void QMailMessagePart::setReferenceResolution(const QString &uri)
+{
+    impl(this)->setReferenceResolution(uri);
+}
+
+static QString randomString(int length)
+{
+    if (length <= 0) 
+        return QString();
+
+    QString str;
+    str.resize( length );
+
+    int i = 0;
+    while (length--){
+        int r=qrand() % 62;
+        r+=48;
+        if (r>57) r+=7;
+        if (r>90) r+=6;
+        str[i++] = char(r);
+    }
+    return str;
+}
+
+static QString partFileName(const QMailMessagePart &part)
+{
+    QString fileName(part.identifier());
+    if (!fileName.isEmpty()) {
+        // Remove any slash characters which are invalid in filenames
+        QChar* first = fileName.data(), *last = first + (fileName.length() - 1);
+        for ( ; last >= first; --last)
+            if (*last == '/')
+                fileName.remove((last - first), 1);
+    }
+
+    // If possible, create the file with a useful filename extension
+    QString existing;
+    int index = fileName.lastIndexOf(".");
+    if (index != -1)
+        existing = fileName.mid(index + 1);
+
+    QStringList extensions = QMail::extensionsForMimeType(part.contentType().content());
+    if (!extensions.isEmpty()) {
+        // See if the existing extension is a known one
+        if (existing.isEmpty() || !extensions.contains(existing, Qt::CaseInsensitive)) {
+            if (!fileName.endsWith(".")) {
+                fileName.append(".");
+            }
+            fileName.append(extensions.first());
+        }
+    }
+
+    return fileName;
+}
+
+/*!
+    Writes the decoded body of the part to a file under the directory specified by \a path.
+    The name of the resulting file is taken from the part. If that file name already exists 
+    in the path a new unique name of the format <random chars>.<filename> is saved.
+
+    Returns the path of the file written on success, or an empty string otherwise.
+*/
+
+QString QMailMessagePart::writeBodyTo(const QString &path) const
+{
+    QString directory(path);
+    if (directory.endsWith("/"))
+        directory.chop(1);
+
+    if (!QDir(directory).exists()) {
+        QDir base;
+        if (QDir::isAbsolutePath(directory))
+            base = QDir::root();
+        else
+            base = QDir::current();
+
+        if (!base.mkpath(directory)) {
+            qWarning() << "Could not create directory to save file " << directory;
+            return QString();
+        }
+    }
+
+    QString fileName(partFileName(*this));
+
+    QString filepath = directory + "/" + fileName;
+    while (QFile::exists(filepath))
+        filepath = directory + "/" + randomString(5) + "." + fileName;
+
+    if (!body().toFile(filepath, QMailMessageBody::Decoded)) {
+        qWarning() << "Could not write part data to file " << filepath;
+        return QString();
+    }
+    
+    return filepath;
+}
+
+/*!
+    Returns an indication of the size of the part.  This measure should be used
+    only in comparing the relative size of parts with respect to transmission.
+*/  
+uint QMailMessagePart::indicativeSize() const
+{
+    return impl(this)->indicativeSize();
+}
+
+/*!
+    Returns true if the entire content of this part is available; otherwise returns false.
+*/
+bool QMailMessagePart::contentAvailable() const
+{
+    return impl(this)->contentAvailable();
+}
+
+/*!
+    Returns true if some portion of the content of this part is available; otherwise returns false.
+*/
+bool QMailMessagePart::partialContentAvailable() const
+{
+    return impl(this)->partialContentAvailable();
+}
+
+/*! \internal */
+void QMailMessagePart::output(QDataStream& out, bool includeAttachments, bool excludeInternalFields) const
+{
+    QDataStream *ds(&out);
+    return impl(this)->output<DummyChunkProcessor>(&ds, false, includeAttachments, excludeInternalFields, 0);
+}
+
+/*! 
+    \fn QMailMessagePart::serialize(Stream&) const
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessagePart::serialize(Stream &stream) const
+{
+    impl(this)->serialize(stream);
+}
+
+template void QMailMessagePart::serialize(QDataStream &) const;
+
+/*! 
+    \fn QMailMessagePart::deserialize(Stream&)
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessagePart::deserialize(Stream &stream)
+{
+    impl(this)->deserialize(stream);
+}
+
+template void QMailMessagePart::deserialize(QDataStream &);
+
+/*! \internal */
+bool QMailMessagePart::contentModified() const
+{
+    return impl(this)->contentModified();
+}
+
+/*! \internal */
+void QMailMessagePart::setUnmodified()
+{
+    impl(this)->setUnmodified();
+}
+
+
+static quint64 incomingFlag = 0;
+static quint64 outgoingFlag = 0;
+static quint64 sentFlag = 0;
+static quint64 repliedFlag = 0;
+static quint64 repliedAllFlag = 0;
+static quint64 forwardedFlag = 0;
+static quint64 contentAvailableFlag = 0;
+static quint64 readFlag = 0;
+static quint64 removedFlag = 0;
+static quint64 readElsewhereFlag = 0;
+static quint64 unloadedDataFlag = 0;
+static quint64 newFlag = 0;
+static quint64 readReplyRequestedFlag = 0;
+static quint64 trashFlag = 0;
+static quint64 partialContentAvailableFlag = 0;
+static quint64 hasAttachmentsFlag = 0;
+static quint64 hasReferencesFlag = 0;
+static quint64 hasUnresolvedReferencesFlag = 0;
+static quint64 draftFlag = 0;
+static quint64 outboxFlag = 0;
+static quint64 junkFlag = 0;
+static quint64 transmitFromExternalFlag = 0;
+static quint64 localOnlyFlag = 0;
+
+
+/*  QMailMessageMetaData */
+
+QMailMessageMetaDataPrivate::QMailMessageMetaDataPrivate()
+    : QPrivateImplementationBase(this),
+      _messageType(QMailMessage::None),
+      _status(0),
+      _contentType(QMailMessage::UnknownContent),
+      _size(0),
+      _responseType(QMailMessage::NoResponse),
+      _customFieldsModified(false),
+      _dirty(false)
+{
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+void QMailMessageMetaDataPrivate::initializeFlags()
+{
+    static bool flagsInitialized = false;
+    if (!flagsInitialized) {
+        flagsInitialized = true;
+
+        incomingFlag = registerFlag("Incoming");
+        outgoingFlag = registerFlag("Outgoing");
+        sentFlag = registerFlag("Sent");
+        repliedFlag = registerFlag("Replied");
+        repliedAllFlag = registerFlag("RepliedAll");
+        forwardedFlag = registerFlag("Forwarded");
+        contentAvailableFlag = registerFlag("ContentAvailable");
+        readFlag = registerFlag("Read");
+        removedFlag = registerFlag("Removed");
+        readElsewhereFlag = registerFlag("ReadElsewhere");
+        unloadedDataFlag = registerFlag("UnloadedData");
+        newFlag = registerFlag("New");
+        readReplyRequestedFlag = registerFlag("ReadReplyRequested");
+        trashFlag = registerFlag("Trash");
+        partialContentAvailableFlag = registerFlag("PartialContentAvailable");
+        hasAttachmentsFlag = registerFlag("HasAttachments");
+        hasReferencesFlag = registerFlag("HasReferences");
+        hasUnresolvedReferencesFlag = registerFlag("HasUnresolvedReferences");
+        draftFlag = registerFlag("Draft");
+        outboxFlag = registerFlag("Outbox");
+        junkFlag = registerFlag("Junk");
+        transmitFromExternalFlag = registerFlag("TransmitFromExternal");
+        localOnlyFlag = registerFlag("LocalOnly");
+    }
+}
+#endif
+
+void QMailMessageMetaDataPrivate::setMessageType(QMailMessage::MessageType type)
+{
+    updateMember(_messageType, type);
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+void QMailMessageMetaDataPrivate::setParentFolderId(const QMailFolderId& id)
+{
+    updateMember(_parentFolderId, id);
+}
+
+void QMailMessageMetaDataPrivate::setPreviousParentFolderId(const QMailFolderId& id)
+{
+    updateMember(_previousParentFolderId, id);
+}
+#endif
+
+void QMailMessageMetaDataPrivate::setId(const QMailMessageId& id)
+{
+    updateMember(_id, id);
+}
+
+void QMailMessageMetaDataPrivate::setStatus(quint64 newStatus)
+{
+    updateMember(_status, newStatus);
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+void QMailMessageMetaDataPrivate::setParentAccountId(const QMailAccountId& id)
+{
+    updateMember(_parentAccountId, id);
+}
+#endif
+
+void QMailMessageMetaDataPrivate::setServerUid(const QString &uid)
+{
+    updateMember(_serverUid, uid);
+}
+
+void QMailMessageMetaDataPrivate::setSize(uint size)
+{
+    updateMember(_size, size);
+}
+
+void QMailMessageMetaDataPrivate::setContent(QMailMessage::ContentType type)
+{
+    updateMember(_contentType, type);
+}
+
+void QMailMessageMetaDataPrivate::setSubject(const QString& s)
+{
+    updateMember(_subject, s);
+}
+
+void QMailMessageMetaDataPrivate::setDate(const QMailTimeStamp& timeStamp)
+{
+    updateMember(_date, timeStamp);
+}
+
+void QMailMessageMetaDataPrivate::setReceivedDate(const QMailTimeStamp& timeStamp)
+{
+    updateMember(_receivedDate, timeStamp);
+}
+
+void QMailMessageMetaDataPrivate::setFrom(const QString& s)
+{
+    updateMember(_from, s);
+} 
+
+void QMailMessageMetaDataPrivate::setTo(const QString& s)
+{
+    updateMember(_to, s);
+}
+
+void QMailMessageMetaDataPrivate::setContentScheme(const QString& scheme)
+{
+    updateMember(_contentScheme, scheme);
+}
+
+void QMailMessageMetaDataPrivate::setContentIdentifier(const QString& identifier)
+{
+    updateMember(_contentIdentifier, identifier);
+}
+
+void QMailMessageMetaDataPrivate::setInResponseTo(const QMailMessageId &id)
+{
+    updateMember(_responseId, id);
+}
+
+void QMailMessageMetaDataPrivate::setResponseType(QMailMessageMetaData::ResponseType type)
+{
+    updateMember(_responseType, type);
+}
+
+uint QMailMessageMetaDataPrivate::indicativeSize() const
+{
+    uint size = (_size / QMailMessageBodyPrivate::IndicativeSizeUnit);
+
+    // Count the message header as one size unit
+    return (size + 1);
+}
+
+bool QMailMessageMetaDataPrivate::dataModified() const
+{
+    return _dirty || _customFieldsModified;
+}
+
+void QMailMessageMetaDataPrivate::setUnmodified()
+{
+    _dirty = false;
+    _customFieldsModified = false;
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+quint64 QMailMessageMetaDataPrivate::registerFlag(const QString &name)
+{
+    if (!QMailStore::instance()->registerMessageStatusFlag(name)) {
+        qMailLog(Messaging) << "Unable to register message status flag:" << name << "!";
+    }
+
+    return QMailMessage::statusMask(name);
+}
+#endif
+
+QString QMailMessageMetaDataPrivate::customField(const QString &name) const
+{
+    QMap<QString, QString>::const_iterator it = _customFields.find(name);
+    if (it != _customFields.end()) {
+        return *it;
+    }
+
+    return QString();
+}
+
+void QMailMessageMetaDataPrivate::setCustomField(const QString &name, const QString &value)
+{
+    QMap<QString, QString>::iterator it = _customFields.find(name);
+    if (it != _customFields.end()) {
+        if (*it != value) {
+            *it = value;
+            _customFieldsModified = true;
+        }
+    } else {
+        _customFields.insert(name, value);
+        _customFieldsModified = true;
+    }
+}
+
+void QMailMessageMetaDataPrivate::removeCustomField(const QString &name)
+{
+    QMap<QString, QString>::iterator it = _customFields.find(name);
+    if (it != _customFields.end()) {
+        _customFields.erase(it);
+        _customFieldsModified = true;
+    }
+}
+
+void QMailMessageMetaDataPrivate::setCustomFields(const QMap<QString, QString> &fields)
+{
+    _customFields = fields;
+    _customFieldsModified = true;
+}
+
+template <typename Stream> 
+void QMailMessageMetaDataPrivate::serialize(Stream &stream) const
+{
+    stream << _messageType;
+    stream << _status;
+    stream << _contentType;
+#ifndef QTOPIAMAIL_PARSING_ONLY
+    stream << _parentAccountId;
+#endif
+    stream << _serverUid;
+    stream << _size;
+    stream << _id;
+#ifndef QTOPIAMAIL_PARSING_ONLY
+    stream << _parentFolderId;
+    stream << _previousParentFolderId;
+#endif
+    stream << _subject;
+    stream << _date.toString();
+    stream << _receivedDate.toString();
+    stream << _from;
+    stream << _to;
+    stream << _contentScheme;
+    stream << _contentIdentifier;
+    stream << _responseId;
+    stream << _responseType;
+    stream << _customFields;
+    stream << _customFieldsModified;
+    stream << _dirty;
+}
+
+template <typename Stream> 
+void QMailMessageMetaDataPrivate::deserialize(Stream &stream)
+{
+    QString timeStamp;
+
+    stream >> _messageType;
+    stream >> _status;
+    stream >> _contentType;
+#ifndef QTOPIAMAIL_PARSING_ONLY
+    stream >> _parentAccountId;
+#endif
+    stream >> _serverUid;
+    stream >> _size;
+    stream >> _id;
+#ifndef QTOPIAMAIL_PARSING_ONLY
+    stream >> _parentFolderId;
+    stream >> _previousParentFolderId;
+#endif
+    stream >> _subject;
+    stream >> timeStamp;
+    _date = QMailTimeStamp(timeStamp);
+    stream >> timeStamp;
+    _receivedDate = QMailTimeStamp(timeStamp);
+    stream >> _from;
+    stream >> _to;
+    stream >> _contentScheme;
+    stream >> _contentIdentifier;
+    stream >> _responseId;
+    stream >> _responseType;
+    stream >> _customFields;
+    stream >> _customFieldsModified;
+    stream >> _dirty;
+}
+
+
+/*!
+    \class QMailMessageMetaData
+
+    \brief The QMailMessageMetaData class provides information about a message stored by Qtopia.
+    
+    \ingroup messaginglibrary
+   
+    The QMailMessageMetaData class provides information about messages stored in the Qt Extended system as QMailMessage objects.  The meta data is more compact and more easily accessed and
+    manipulated than the content of the message itself.  Many messaging-related tasks can 
+    be accomplished by manipulating the message meta data, such as listing, filtering, and 
+    searching through sets of messages.
+
+    QMailMessageMetaData objects can be created as needed, specifying the identifier of
+    the message whose meta data is required.  The meta data of a message can be located by 
+    specifying the QMailMessageId identifier directly, or by specifying the account and server UID 
+    pair needed to locate the message.
+
+    The content of the message described by the meta data object can be accessed by creating 
+    a QMailMessage object specifying the identifier returned by QMailMessageMetaData::id().
+
+    \sa QMailStore, QMailMessageId
+*/    
+    
+/*!
+    \typedef QMailMessageMetaData::ImplementationType
+    \internal
+*/
+
+/*!
+    \variable QMailMessageMetaData::Incoming
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Incoming" against the result of QMailMessage::status().
+
+    This flag indicates that the message has been sent from an external source to an
+    account whose messages are retrieved to Qt Extended.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Outgoing
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Outgoing" against the result of QMailMessage::status().
+
+    This flag indicates that the message originates within Qt Extended, for transmission 
+    to an external message sink.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Sent
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Sent" against the result of QMailMessage::status().
+
+    This flag indicates that the message has been delivered to an external message sink.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Replied
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Replied" against the result of QMailMessage::status().
+
+    This flag indicates that a message replying to the source of this message has been 
+    created, in response to this message.
+*/
+
+/*!
+    \variable QMailMessageMetaData::RepliedAll
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "RepliedAll" against the result of QMailMessage::status().
+
+    This flag indicates that a message replying to the source of this message and all 
+    its recipients, has been created in response to this message.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Forwarded
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Forwarded" against the result of QMailMessage::status().
+
+    This flag indicates that a message forwarding the content of this message has been created.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Read
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Read" against the result of QMailMessage::status().
+
+    This flag indicates that the content of this message has been displayed to the user.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Removed
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Removed" against the result of QMailMessage::status().
+
+    This flag indicates that the message has been deleted from or moved on the originating server.
+*/
+
+/*!
+    \variable QMailMessageMetaData::ReadElsewhere
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "ReadElsewhere" against the result of QMailMessage::status().
+
+    This flag indicates that the content of this message has been reported as having
+    been displayed to the user by some other client.
+*/
+
+/*!
+    \variable QMailMessageMetaData::UnloadedData
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "UnloadedData" against the result of QMailMessage::status().
+
+    This flag indicates that the meta data of the message is not loaded in entirety.
+*/
+
+/*!
+    \variable QMailMessageMetaData::New
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "New" against the result of QMailMessage::status().
+
+    This flag indicates that the meta data of the message has not yet been displayed to the user.
+*/
+
+/*!
+    \variable QMailMessageMetaData::ReadReplyRequested
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "ReadReplyRequested" against the result of QMailMessage::status().
+
+    This flag indicates that the message has requested that a read confirmation reply be returned to the sender.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Trash
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Trash" against the result of QMailMessage::status().
+
+    This flag indicates that the message has been marked as trash, and should be considered logically deleted.
+*/
+
+/*!
+    \variable QMailMessageMetaData::ContentAvailable
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "ContentAvailable" against the result of QMailMessage::status().
+
+    This flag indicates that the entire content of the message has been retrieved from the originating server,
+    excluding any sub-parts of the message.
+
+    \sa QMailMessagePartContainer::contentAvailable()
+*/
+
+/*!
+    \variable QMailMessageMetaData::PartialContentAvailable
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "PartialContentAvailable" against the result of QMailMessage::status().
+
+    This flag indicates that some portion of the  content of the message has been retrieved from the originating server.
+
+    \sa QMailMessagePartContainer::contentAvailable()
+*/
+
+/*!
+    \variable QMailMessageMetaData::HasAttachments
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "HasAttachments" against the result of QMailMessage::status().
+
+    This flag indicates that the message contains at least one sub-part with 'Attachment' disposition.
+
+    \sa QMailMessageContentDisposition
+*/
+
+/*!
+    \variable QMailMessageMetaData::HasReferences
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "HasReferences" against the result of QMailMessage::status().
+
+    This flag indicates that the message contains at least one sub-part which is a reference to an external message element.
+
+    \sa QMailMessagePart::referenceType()
+*/
+
+/*!
+    \variable QMailMessageMetaData::HasUnresolvedReferences
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "HasUnresolvedReferences" against the result of QMailMessage::status().
+
+    This flag indicates that the message contains at least one sub-part which is a reference, that has no corresponding resolution value.
+
+    \sa QMailMessagePart::referenceType(), QMailMessagePart::referenceResolution()
+*/
+
+/*!
+    \variable QMailMessageMetaData::Draft
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Draft" against the result of QMailMessage::status().
+
+    This flag indicates that the message has been marked as a draft, and should be considered subject to further composition.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Outbox
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Outbox" against the result of QMailMessage::status().
+
+    This flag indicates that the message has been marked as ready for transmission.
+*/
+
+/*!
+    \variable QMailMessageMetaData::Junk
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "Junk" against the result of QMailMessage::status().
+
+    This flag indicates that the message has been marked as junk, and should be considered unsuitable for standard listings.
+*/
+
+/*!
+    \variable QMailMessageMetaData::TransmitFromExternal
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "TransmitFromExternal" against the result of QMailMessage::status().
+
+    This flag indicates that the message should be transmitted by reference to its external server location.
+*/
+
+/*!
+    \variable QMailMessageMetaData::LocalOnly
+
+    The status mask needed for testing the value of the registered status flag named 
+    \c "LocalOnly" against the result of QMailMessage::status().
+
+    This flag indicates that the message exists only on the local device, and has no representation on any external server.
+*/
+
+const quint64 &QMailMessageMetaData::Incoming = incomingFlag;
+const quint64 &QMailMessageMetaData::Outgoing = outgoingFlag;
+const quint64 &QMailMessageMetaData::Sent = sentFlag;
+const quint64 &QMailMessageMetaData::Replied = repliedFlag;
+const quint64 &QMailMessageMetaData::RepliedAll = repliedAllFlag;
+const quint64 &QMailMessageMetaData::Forwarded = forwardedFlag;
+const quint64 &QMailMessageMetaData::ContentAvailable = contentAvailableFlag;
+const quint64 &QMailMessageMetaData::Read = readFlag;
+const quint64 &QMailMessageMetaData::Removed = removedFlag;
+const quint64 &QMailMessageMetaData::ReadElsewhere = readElsewhereFlag;
+const quint64 &QMailMessageMetaData::UnloadedData = unloadedDataFlag;
+const quint64 &QMailMessageMetaData::New = newFlag;
+const quint64 &QMailMessageMetaData::ReadReplyRequested = readReplyRequestedFlag;
+const quint64 &QMailMessageMetaData::Trash = trashFlag;
+const quint64 &QMailMessageMetaData::PartialContentAvailable = partialContentAvailableFlag;
+const quint64 &QMailMessageMetaData::HasAttachments = hasAttachmentsFlag;
+const quint64 &QMailMessageMetaData::HasReferences = hasReferencesFlag;
+const quint64 &QMailMessageMetaData::HasUnresolvedReferences = hasUnresolvedReferencesFlag;
+const quint64 &QMailMessageMetaData::Draft = draftFlag;
+const quint64 &QMailMessageMetaData::Outbox = outboxFlag;
+const quint64 &QMailMessageMetaData::Junk = junkFlag;
+const quint64 &QMailMessageMetaData::TransmitFromExternal = transmitFromExternalFlag;
+const quint64 &QMailMessageMetaData::LocalOnly = localOnlyFlag;
+
+/*!
+    Constructs an empty message meta data object.
+*/
+QMailMessageMetaData::QMailMessageMetaData()
+    : QPrivatelyImplemented<QMailMessageMetaDataPrivate>(new QMailMessageMetaDataPrivate())
+{
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+/*!
+    Constructs a message meta data object from data stored in the message store with QMailMessageId \a id.
+*/
+QMailMessageMetaData::QMailMessageMetaData(const QMailMessageId& id)
+    : QPrivatelyImplemented<QMailMessageMetaDataPrivate>(0)
+{
+    *this = QMailStore::instance()->messageMetaData(id);
+}
+
+/*!
+    Constructs a message meta data object from data stored in the message store with the unique 
+    identifier \a uid from the account with id \a accountId.
+*/
+QMailMessageMetaData::QMailMessageMetaData(const QString& uid, const QMailAccountId& accountId)
+    : QPrivatelyImplemented<QMailMessageMetaDataPrivate>(0)
+{
+    *this = QMailStore::instance()->messageMetaData(uid, accountId);
+}
+#endif
+
+/*!
+    Sets the MessageType of the message to \a type.
+
+    \sa messageType()
+*/
+void QMailMessageMetaData::setMessageType(QMailMessageMetaData::MessageType type)
+{
+    switch (type) {
+        case QMailMessage::Mms:
+        case QMailMessage::Sms:
+        case QMailMessage::Email:
+        case QMailMessage::Instant:
+        case QMailMessage::System:
+            break;
+        default:
+            qWarning() << "QMailMessageMetaData::setMessageType:" << type;
+            return;
+    }
+
+    impl(this)->setMessageType(type);
+}
+
+/*!
+    Returns the MessageType of the message.
+
+    \sa setMessageType()
+*/
+QMailMessageMetaData::MessageType QMailMessageMetaData::messageType() const
+{
+    return impl(this)->_messageType;
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+/*!
+    Return the QMailFolderId of the folder that contains the message.
+*/
+QMailFolderId QMailMessageMetaData::parentFolderId() const
+{
+    return impl(this)->_parentFolderId;
+}
+
+/*!
+    Sets the QMailFolderId of the folder that contains the message to \a id.
+*/
+void QMailMessageMetaData::setParentFolderId(const QMailFolderId &id)
+{
+    impl(this)->setParentFolderId(id);
+}
+#endif
+
+/*!
+    Returns the Qt Extended unique QMailMessageId of the message.
+*/
+QMailMessageId QMailMessageMetaData::id() const
+{
+    return impl(this)->_id;
+}
+
+/*!
+    Sets the QMailMessageId of the message to \a id.
+    \a id should be different for each message known to Qtopia.
+*/
+void QMailMessageMetaData::setId(const QMailMessageId &id)
+{
+    impl(this)->setId(id);
+}
+
+/*!
+    Returns the originating address of the message.
+*/
+QMailAddress QMailMessageMetaData::from() const
+{
+    return QMailAddress(impl(this)->_from);
+}
+
+/*!
+    Sets the from address, that is the originating address of the message to \a from.
+*/
+void QMailMessageMetaData::setFrom(const QMailAddress &from)
+{
+    impl(this)->setFrom(from.toString());
+}
+
+/*!
+    Returns the subject of the message, if present; otherwise returns an empty string.
+*/
+QString QMailMessageMetaData::subject() const
+{
+    return impl(this)->_subject;
+}
+
+/*!
+    Sets the subject of the message to \a subject.
+*/
+void QMailMessageMetaData::setSubject(const QString &subject)
+{
+    impl(this)->setSubject(subject);
+}
+
+
+/*!
+    Returns the timestamp contained in the origination date header field of the message, if present; 
+    otherwise returns an empty timestamp.
+*/
+QMailTimeStamp QMailMessageMetaData::date() const
+{
+    return QMailTimeStamp(impl(this)->_date);
+}
+
+/*!
+    Sets the origination date header field specifying the timestamp of the message to \a timeStamp.
+*/
+void QMailMessageMetaData::setDate(const QMailTimeStamp &timeStamp)
+{
+    impl(this)->setDate(timeStamp);
+}
+
+/*!
+    Returns the timestamp placed in the message during reception by the messageserver, if present;
+    otherwise returns an empty timestamp.
+*/
+QMailTimeStamp QMailMessageMetaData::receivedDate() const
+{
+    return QMailTimeStamp(impl(this)->_receivedDate);
+}
+
+/*!
+    Sets the timestamp indicating the time of message reception by the messageserver to \a timeStamp.
+*/
+void QMailMessageMetaData::setReceivedDate(const QMailTimeStamp &timeStamp)
+{
+    impl(this)->setReceivedDate(timeStamp);
+}
+
+/*! 
+    Returns the list of primary recipients for the message.
+
+    \sa QMailAddress
+*/
+QList<QMailAddress> QMailMessageMetaData::to() const
+{
+    return QMailAddress::fromStringList(impl(this)->_to);
+}
+
+/*! 
+    Sets the list of primary recipients for the message to \a toList.
+*/
+void QMailMessageMetaData::setTo(const QList<QMailAddress>& toList)
+{
+    impl(this)->setTo(QMailAddress::toStringList(toList).join(","));
+}
+
+/*! 
+    Sets the list of primary recipients for the message to contain \a address.
+*/
+void QMailMessageMetaData::setTo(const QMailAddress& address)
+{
+    setTo(QList<QMailAddress>() << address);
+}
+
+/*!
+    Returns the status value for the message.
+
+    \sa setStatus(), statusMask()
+*/  
+quint64 QMailMessageMetaData::status() const
+{
+    return impl(this)->_status;
+}
+
+/*!
+    Sets the status value for the message to \a newStatus.
+
+    \sa status(), statusMask()
+*/
+void QMailMessageMetaData::setStatus(quint64 newStatus)
+{
+    impl(this)->setStatus(newStatus);
+}
+
+/*!
+    Sets the status flags indicated in \a mask to \a set.
+
+    \sa status(), statusMask()
+*/
+void QMailMessageMetaData::setStatus(quint64 mask, bool set)
+{
+    quint64 newStatus = impl(this)->_status;
+
+    if (set)
+        newStatus |= mask;
+    else
+        newStatus &= ~mask;
+    impl(this)->setStatus(newStatus);
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+/*!
+    Returns the id of the originating account for the message.
+*/  
+QMailAccountId QMailMessageMetaData::parentAccountId() const
+{
+    return impl(this)->_parentAccountId;
+}
+
+/*!
+    Sets the id of the originating account for the message to \a id.
+*/ 
+void QMailMessageMetaData::setParentAccountId(const QMailAccountId& id)
+{
+    impl(this)->setParentAccountId(id);
+}
+#endif
+
+/*!
+    Returns the identifier for the message on the originating server.
+*/  
+QString QMailMessageMetaData::serverUid() const
+{
+    return impl(this)->_serverUid;
+}
+
+/*!
+    Sets the originating server identifier for the message to \a server.
+    The identifier specified should be unique.
+*/  
+void QMailMessageMetaData::setServerUid(const QString &server)
+{
+    impl(this)->setServerUid(server);
+}
+
+/*!
+    Returns the complete size of the message as indicated on the originating server.
+*/  
+uint QMailMessageMetaData::size() const
+{
+    return impl(this)->_size;
+}
+
+/*!
+    Sets the complete size of the message as found on the server to \a size.
+*/  
+void QMailMessageMetaData::setSize(uint size)
+{
+    impl(this)->setSize(size);
+}
+
+/*!
+    Returns an indication of the size of the message.  This measure should be used
+    only in comparing the relative size of messages with respect to transmission.
+*/  
+uint QMailMessageMetaData::indicativeSize() const
+{
+    return impl(this)->indicativeSize();
+}
+
+/*!
+    Returns the type of content contained within the message.
+*/  
+QMailMessage::ContentType QMailMessageMetaData::content() const
+{
+    return impl(this)->_contentType;
+}
+
+/*!
+    \fn QMailMessageMetaData::setContent(QMailMessageMetaData::ContentType)
+
+    Sets the type of content contained within the message to \a type.  
+    It is the caller's responsibility to ensure that this value matches the actual content.
+*/  
+void QMailMessageMetaData::setContent(QMailMessage::ContentType type)
+{
+    impl(this)->setContent(type);
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+/*!
+    Return the QMailFolderId of the folder that contained the message before it was 
+    moved into the current parent folder.
+*/
+QMailFolderId QMailMessageMetaData::previousParentFolderId() const
+{
+    return impl(this)->_previousParentFolderId;
+}
+
+/*!
+    Sets the QMailFolderId of the folder that contained the message before it was 
+    moved into the current parent folder to \a id.
+*/
+void QMailMessageMetaData::setPreviousParentFolderId(const QMailFolderId &id)
+{
+    impl(this)->setPreviousParentFolderId(id);
+}
+#endif
+
+/*!
+    Returns the scheme used to store the content of this message.
+*/  
+QString QMailMessageMetaData::contentScheme() const
+{
+    return impl(this)->_contentScheme;
+}
+
+/*!
+    Sets the scheme used to store the content of this message to \a scheme, and returns
+    true if successful.  Once set, the scheme cannot be modified.
+*/  
+bool QMailMessageMetaData::setContentScheme(const QString &scheme)
+{
+    if (!impl(this)->_contentScheme.isEmpty() && (impl(this)->_contentScheme != scheme)) {
+        qMailLog(Messaging) << "Warning - modifying existing content scheme from:" << impl(this)->_contentScheme << "to:" << scheme;
+    }
+
+    impl(this)->setContentScheme(scheme);
+    return true;
+}
+
+/*!
+    Returns the identifer used to locate the content of this message.
+*/  
+QString QMailMessageMetaData::contentIdentifier() const
+{
+    return impl(this)->_contentIdentifier;
+}
+
+/*!
+    Sets the identifer used to locate the content of this message to \a identifier, and returns
+    true if successful.  Once set, the identifier cannot be modified.
+
+    The identifier specified should be unique within the scheme returned by contentScheme().
+*/  
+bool QMailMessageMetaData::setContentIdentifier(const QString &identifier)
+{
+    impl(this)->setContentIdentifier(identifier);
+    return true;
+}
+
+/*!
+    Returns the identifier of the message that this message was created in response to.
+*/  
+QMailMessageId QMailMessageMetaData::inResponseTo() const
+{
+    return impl(this)->_responseId;
+}
+
+/*!
+    Sets the identifier of the message that this message was created in response to, to \a id.
+*/
+void QMailMessageMetaData::setInResponseTo(const QMailMessageId &id)
+{
+    impl(this)->setInResponseTo(id);
+}
+
+/*!
+    Returns the type of response that this message was created as.
+
+    \sa inResponseTo()
+*/  
+QMailMessageMetaData::ResponseType QMailMessageMetaData::responseType() const
+{
+    return impl(this)->_responseType;
+}
+
+/*!
+    Sets the type of response that this message was created as to \a type.
+
+    \sa setInResponseTo()
+*/
+void QMailMessageMetaData::setResponseType(QMailMessageMetaData::ResponseType type)
+{
+    impl(this)->setResponseType(type);
+}
+
+/*!
+    Returns true if the entire content of this message is available; otherwise returns false.
+*/
+bool QMailMessageMetaData::contentAvailable() const
+{
+    return (status() & QMailMessage::ContentAvailable);
+}
+
+/*!
+    Returns true if some portion of the content of this message is available; otherwise returns false.
+*/
+bool QMailMessageMetaData::partialContentAvailable() const
+{
+    return (status() & QMailMessage::PartialContentAvailable);
+}
+
+/*! \internal */
+bool QMailMessageMetaData::dataModified() const
+{
+    return impl(this)->dataModified();
+}
+
+/*! \internal */
+void QMailMessageMetaData::setUnmodified()
+{
+    impl(this)->setUnmodified();
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+/*!
+    Returns the status bitmask needed to test the result of QMailMessageMetaData::status() 
+    against the QMailMessageMetaData status flag registered with the identifier \a flagName.
+
+    \sa status(), QMailStore::messageStatusMask()
+*/
+quint64 QMailMessageMetaData::statusMask(const QString &flagName)
+{
+    return QMailStore::instance()->messageStatusMask(flagName);
+}
+#endif
+
+/*! 
+    Returns the value recorded in the custom field named \a name.
+
+    \sa setCustomField(), customFields()
+*/
+QString QMailMessageMetaData::customField(const QString &name) const
+{
+    return d->customField(name);
+}
+
+/*! 
+    Sets the value of the custom field named \a name to \a value.
+
+    \sa customField(), customFields()
+*/
+void QMailMessageMetaData::setCustomField(const QString &name, const QString &value)
+{
+    d->setCustomField(name, value);
+}
+
+/*! 
+    Removes the custom field named \a name.
+
+    \sa customField(), customFields()
+*/
+void QMailMessageMetaData::removeCustomField(const QString &name)
+{
+    d->removeCustomField(name);
+}
+
+/*! 
+    Returns the map of custom fields stored in the message.
+
+    \sa customField(), setCustomField()
+*/
+const QMap<QString, QString> &QMailMessageMetaData::customFields() const
+{
+    return d->_customFields;
+}
+
+/*! \internal */
+void QMailMessageMetaData::setCustomFields(const QMap<QString, QString> &fields)
+{
+    d->setCustomFields(fields);
+}
+
+/*! \internal */
+bool QMailMessageMetaData::customFieldsModified() const
+{
+    return d->_customFieldsModified;
+}
+
+/*! \internal */
+void QMailMessageMetaData::setCustomFieldsModified(bool set)
+{
+    d->_customFieldsModified = set;
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+/*! \internal */
+void QMailMessageMetaData::initStore()
+{
+    QMailMessageMetaDataPrivate::initializeFlags();
+}
+#endif
+
+/*! 
+    \fn QMailMessageMetaData::serialize(Stream&) const
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageMetaData::serialize(Stream &stream) const
+{
+    impl(this)->serialize(stream);
+}
+
+template void QMailMessageMetaData::serialize(QDataStream &) const;
+
+/*! 
+    \fn QMailMessageMetaData::deserialize(Stream&)
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessageMetaData::deserialize(Stream &stream)
+{
+    impl(this)->deserialize(stream);
+}
+
+template void QMailMessageMetaData::deserialize(QDataStream &);
+
+
+/*  QMailMessage */
+
+QMailMessagePrivate::QMailMessagePrivate()
+    : QMailMessagePartContainerPrivate(this)
+{
+}
+
+void QMailMessagePrivate::fromRfc2822(const LongString &ls)
+{
+    _messageParts.clear();
+
+    if (ls.length()) {
+        QMailMessageContentType contentType(headerField("Content-Type"));
+
+        // Is this a simple mail or a multi-part collection?
+        QByteArray mimeVersion = headerField("MIME-Version");
+        QByteArray minimalVersion = QMailMessageHeaderField::removeWhitespace(QMailMessageHeaderField::removeComments(mimeVersion));
+        if (!mimeVersion.isEmpty() && (minimalVersion != "1.0")) {
+            qWarning() << "Unknown MIME-Version:" << mimeVersion;
+        } else if (_multipartType != QMailMessagePartContainer::MultipartNone) {
+            parseMimeMultipart(_header, ls, true);
+        } else {
+            QByteArray bodyData;
+
+            // Remove the pop-style terminator if present
+            const QByteArray popTerminator((QByteArray(QMailMessage::CRLF) + '.' + QMailMessage::CRLF));
+            if ( ls.indexOf(popTerminator, -popTerminator.length()) != -1)
+                bodyData = ls.left( ls.length() - popTerminator.length() ).toQByteArray();
+            else
+                bodyData = ls.toQByteArray();
+
+            // The body data is already encoded
+            QDataStream in(bodyData);
+            QMailMessageBody::TransferEncoding encoding = encodingForName(headerField("Content-Transfer-Encoding"));
+            if ( encoding == QMailMessageBody::NoEncoding )
+                encoding = QMailMessageBody::SevenBit;
+
+            setBody( QMailMessageBody::fromStream(in, contentType, encoding, QMailMessageBody::AlreadyEncoded) );
+        }
+    }
+}
+
+void QMailMessagePrivate::setId(const QMailMessageId& id)
+{
+    setLocation(id, _indices);
+}
+
+void QMailMessagePrivate::setSubject(const QString& s)
+{
+    updateHeaderField( "Subject:", s );
+}
+
+void QMailMessagePrivate::setDate(const QMailTimeStamp& timeStamp)
+{
+    updateHeaderField( "Date:", to7BitAscii(timeStamp.toString()) );
+}
+
+void QMailMessagePrivate::setFrom(const QString& s)
+{
+    updateHeaderField( "From:", s );
+} 
+
+void QMailMessagePrivate::setReplyTo(const QString& s)
+{
+    updateHeaderField( "Reply-To:", s );
+}
+
+void QMailMessagePrivate::setInReplyTo(const QString& s)
+{
+    updateHeaderField( "In-Reply-To:", s );
+}
+
+void QMailMessagePrivate::setTo(const QString& s)
+{
+    updateHeaderField( "To:", s );
+}
+
+void QMailMessagePrivate::setBcc(const QString& s)
+{
+    updateHeaderField( "Bcc:", s );
+}
+
+void QMailMessagePrivate::setCc(const QString& s)
+{
+    updateHeaderField( "Cc:", s );
+}
+
+bool QMailMessagePrivate::hasRecipients() const
+{
+    if ( !headerField("To").isEmpty() )
+        return true;
+    if ( !headerField("Cc").isEmpty() )
+        return true;
+    if ( !headerField("Bcc").isEmpty() )
+        return true;
+
+    return false;
+}
+
+uint QMailMessagePrivate::indicativeSize() const
+{
+    uint size = QMailMessagePartContainerPrivate::indicativeSize(); 
+
+    // Count the message header as one size unit
+    return (size + 1);
+}
+
+static uint currentTimeValue()
+{
+    return QDateTime::currentDateTime().toTime_t();
+}
+
+static bool seedRng()
+{
+    qsrand(currentTimeValue());
+    return true;
+}
+
+static int randomNumber()
+{
+    static bool initialised = seedRng();
+    return qrand();
+
+    Q_UNUSED(initialised)
+}
+
+static QByteArray gBoundaryString;
+
+void QTOPIAMAIL_EXPORT setQMailMessageBoundaryString(const QByteArray &boundary)
+{
+    gBoundaryString = boundary;
+}
+
+static QByteArray boundaryString(const QByteArray &hash)
+{
+    static const QByteArray boundaryLeader = "[)}<";
+    static const QByteArray boundaryTrailer = ")}<]";
+
+    if (!gBoundaryString.isEmpty())
+        return gBoundaryString;
+
+    // Formulate a boundary that is very unlikely to clash with the content
+    return boundaryLeader + "qtopiamail:" + QByteArray::number(randomNumber()) + hash.toBase64() + boundaryTrailer;
+}
+
+template <typename F>
+void QMailMessagePrivate::toRfc2822(QDataStream **out, QMailMessage::EncodingFormat format, quint64 messageStatus, F *func) const
+{
+    bool isOutgoing = (messageStatus & (QMailMessage::Outgoing | QMailMessage::Sent));
+
+    bool addTimeStamp = (format != QMailMessage::IdentityFormat);
+    bool addContentHeaders = ((format != QMailMessage::IdentityFormat) && 
+                              ((format != QMailMessage::StorageFormat) || isOutgoing || !hasBody()));
+    bool includeBcc = (format != QMailMessage::TransmissionFormat);
+    bool excludeInternalFields = (format == QMailMessage::TransmissionFormat);
+
+    if (_messageParts.count() && boundary().isEmpty()) {
+        // Include a hash of the header data in the boundary
+        QCryptographicHash hash(QCryptographicHash::Md5);
+        foreach (const QByteArray* field, _header.fieldList())
+            hash.addData(*field);
+
+        const_cast<QMailMessagePrivate*>(this)->setBoundary(boundaryString(hash.result()));
+    }
+
+    outputHeaders(**out, addTimeStamp, addContentHeaders, includeBcc, excludeInternalFields);
+    **out << DataString('\n');
+
+    if (format != QMailMessage::HeaderOnlyFormat) {
+        if ( hasBody() ) {
+            outputBody( **out, true); //not multipart so part should not be an attachment
+        } else {
+            bool addMimePreamble = (format == QMailMessage::TransmissionFormat);
+            bool includeAttachments = (format != QMailMessage::StorageFormat);
+
+            outputParts<F>( out, addMimePreamble, includeAttachments, excludeInternalFields, func );
+        }
+    }
+}
+
+void QMailMessagePrivate::outputHeaders( QDataStream& out, bool addTimeStamp, bool addContentHeaders, bool includeBcc, bool excludeInternalFields ) const
+{
+    QList<QByteArray> exclusions;
+
+    if (addContentHeaders) {
+        // Don't include the nominated MIME-Version if specified - we implement 1.0
+        exclusions.append("MIME-Version");
+    }
+    if (!includeBcc) {
+        exclusions.append("bcc");
+    }
+
+    _header.output( out, exclusions, excludeInternalFields );
+
+    if (addTimeStamp && headerField("Date").isEmpty()) {
+        QString timeStamp = QMailTimeStamp( QDateTime::currentDateTime() ).toString();
+        out << DataString("Date: ") << DataString(to7BitAscii(timeStamp)) << DataString('\n');
+    }
+
+    if (addContentHeaders) {
+        // Output required content header fields
+        out << DataString("MIME-Version: 1.0") << DataString('\n');
+    }
+}
+
+bool QMailMessagePrivate::contentModified() const
+{
+    // For this part of any sub-part
+    return dirty(true);
+}
+
+void QMailMessagePrivate::setUnmodified()
+{
+    setDirty(false, true);
+}
+
+template <typename Stream> 
+void QMailMessagePrivate::serialize(Stream &stream) const
+{
+    QMailMessagePartContainerPrivate::serialize(stream);
+}
+
+template <typename Stream> 
+void QMailMessagePrivate::deserialize(Stream &stream)
+{
+    QMailMessagePartContainerPrivate::deserialize(stream);
+}
+
+
+//===========================================================================
+
+/*!
+    \class QMailMessage
+
+    \brief The QMailMessage class provides a convenient interface for working with messages.
+    
+    \ingroup messaginglibrary
+   
+    QMailMessage supports a number of types. These include telephony types 
+    such as SMS and MMS, and internet email messages as defined in 
+    \l{http://www.ietf.org/rfc/rfc2822.txt} {RFC 2822} (Internet Message Format), and 
+    \l{http://www.ietf.org/rfc/rfc2045.txt} {RFC 2045} (Format of Internet Message Bodies) through 
+    \l{http://www.ietf.org/rfc/rfc2049.txt} {RFC 2049} (Conformance Criteria and Examples).
+    
+    The most common way to use QMailMessage is to initialize it from raw
+    data using QMailMessage::fromRfc2822().
+    
+    A QMailMessage can also be constructed piece by piece using functions such as 
+    setMessageType(), setFrom(), setTo(), setSubject(), and setBody() or appendPart(). 
+    Convenience functions such as from()/setFrom() and date()/setDate() accept and
+    return wrapper types, to simplify the exchange of correctly-formatted data.
+    In some cases, however, it may be simpler for clients to get and set the content 
+    of header fields directly, using the headerField() and setHeaderField() functions inherited
+    from QMailMessagePartContainer.
+    
+    Messages can be added to the QMailStore, or retrieved from the store via their QMailMessageId 
+    identifier.  The QMailMessage object also provides acces to any relevant meta data
+    describing the message, using the functions inherited from QMailMessageMetaData.
+
+    A message may be serialized to a QDataStream, or returned as a QByteArray using toRfc2822().
+    
+    \sa QMailMessageMetaData, QMailMessagePart, QMailMessageBody, QMailStore, QMailMessageId
+*/    
+
+
+const char QMailMessage::CarriageReturn = '\015';
+const char QMailMessage::LineFeed = '\012';
+const char* QMailMessage::CRLF = "\015\012";
+
+/*!
+    Constructs an empty message object.
+*/
+QMailMessage::QMailMessage()
+    : QMailMessageMetaData(),
+      QMailMessagePartContainer(new QMailMessagePrivate())
+{
+}
+
+#ifndef QTOPIAMAIL_PARSING_ONLY
+/*!
+    Constructs a message object from data stored in the message store with QMailMessageId \a id.
+*/
+QMailMessage::QMailMessage(const QMailMessageId& id)
+    : QMailMessageMetaData(id),
+      QMailMessagePartContainer(reinterpret_cast<QMailMessagePrivate*>(0))
+{
+    *this = QMailStore::instance()->message(id);
+}
+
+/*!
+    Constructs a message object from data stored in the message store with the unique 
+    identifier \a uid from the account with id \a accountId.
+*/
+QMailMessage::QMailMessage(const QString& uid, const QMailAccountId& accountId)
+    : QMailMessageMetaData(uid, accountId),
+      QMailMessagePartContainer(reinterpret_cast<QMailMessagePrivate*>(0))
+{
+    *this = QMailStore::instance()->message(uid, accountId);
+}
+#endif
+
+/*!
+    Constructs a mail message from the RFC 2822 data contained in \a byteArray.
+*/
+QMailMessage QMailMessage::fromRfc2822(const QByteArray &byteArray)
+{
+    LongString ls(byteArray);
+    return fromRfc2822(ls);
+}
+
+/*!
+    Constructs a mail message from the RFC 2822 data contained in the file \a fileName.
+*/
+QMailMessage QMailMessage::fromRfc2822File(const QString& fileName)
+{
+    LongString ls(fileName);
+    return fromRfc2822(ls);
+}
+
+/*!
+    Returns true if the message contains a part with the location \a location.
+*/
+bool QMailMessage::contains(const QMailMessagePart::Location& location) const
+{
+    return partContainerImpl()->contains(location);
+}
+
+/*!
+    Returns a const reference to the part at the location \a location within the message.
+*/
+const QMailMessagePart& QMailMessage::partAt(const QMailMessagePart::Location& location) const
+{
+    return partContainerImpl()->partAt(location);
+}
+
+/*!
+    Returns a non-const reference to the part at the location \a location within the message.
+*/
+QMailMessagePart& QMailMessage::partAt(const QMailMessagePart::Location& location)
+{
+    return partContainerImpl()->partAt(location);
+}
+
+/*! \reimp */
+void QMailMessage::setHeaderField( const QString& id, const QString& value )
+{
+    QMailMessagePartContainer::setHeaderField(id, value);
+
+    QByteArray duplicatedId(duplicatedData(id));
+    if (!duplicatedId.isNull()) {
+        updateMetaData(duplicatedId, value);
+    }
+}
+
+/*! \reimp */
+void QMailMessage::setHeaderField( const QMailMessageHeaderField& field )
+{
+    setHeaderField(field.id(), field.toString(false, false));
+}
+
+/*! \reimp */
+void QMailMessage::appendHeaderField( const QString& id, const QString& value )
+{
+    QMailMessagePartContainer::appendHeaderField(id, value);
+
+    QByteArray duplicatedId(duplicatedData(id));
+    if (!duplicatedId.isNull()) {
+        // We need to keep the value of the first item with this ID in the meta data object
+        updateMetaData(duplicatedId, headerFieldText(duplicatedId));
+    }
+}
+
+/*! \reimp */
+void QMailMessage::appendHeaderField( const QMailMessageHeaderField& field )
+{
+    appendHeaderField(field.id(), field.toString(false, false));
+}
+
+/*! \reimp */
+void QMailMessage::removeHeaderField( const QString& id )
+{
+    QMailMessagePartContainer::removeHeaderField(id);
+
+    QByteArray duplicatedId(duplicatedData(id));
+    if (!duplicatedId.isNull()) {
+        updateMetaData(duplicatedId, QString());
+    }
+}
+
+/*!
+    Returns the message in RFC 2822 format. The encoded content will vary depending on the value of \a format.
+*/
+QByteArray QMailMessage::toRfc2822(EncodingFormat format) const
+{
+    QByteArray result;
+    {
+        QDataStream out(&result, QIODevice::WriteOnly);
+        toRfc2822(out, format);
+    }
+    return result;
+}
+
+/*!
+    Writes the message to the output stream \a out, in RFC 2822 format. 
+    The encoded content will vary depending on the value of \a format.
+*/
+void QMailMessage::toRfc2822(QDataStream& out, EncodingFormat format) const
+{
+    QDataStream *ds(&out);
+    partContainerImpl()->toRfc2822<DummyChunkProcessor>(&ds, format, status(), 0);
+}
+
+struct ChunkStore
+{
+    QList<QMailMessage::MessageChunk> chunks;
+    QByteArray chunk;
+    QDataStream *ds;
+
+    ChunkStore()
+        : ds(new QDataStream(&chunk, QIODevice::WriteOnly | QIODevice::Unbuffered))
+    {
+    }
+
+    ~ChunkStore()
+    {
+        close();
+    }
+
+    void close() 
+    {
+        if (ds) {
+            delete ds;
+            ds = 0;
+
+            if (!chunk.isEmpty()) {
+                chunks.append(qMakePair(QMailMessage::Text, chunk));
+            }
+        }
+    }
+
+    void operator()(QMailMessage::ChunkType type)
+    {
+        // This chunk is now complete
+        delete ds;
+        chunks.append(qMakePair(type, chunk));
+
+        chunk.clear();
+        ds = new QDataStream(&chunk, QIODevice::WriteOnly | QIODevice::Unbuffered);
+    }
+};
+
+/*!
+    Returns the message in RFC 2822 format, separated into chunks that can
+    be individually transferred by a mechanism such as that defined by RFC 3030.
+    The encoded content will vary depending on the value of \a format.
+*/
+QList<QMailMessage::MessageChunk> QMailMessage::toRfc2822Chunks(EncodingFormat format) const
+{
+    ChunkStore store;
+
+    partContainerImpl()->toRfc2822<ChunkStore>(&store.ds, format, status(), &store);
+    store.close();
+
+    return store.chunks;
+}
+
+/*! \reimp */
+void QMailMessage::setId(const QMailMessageId &id)
+{
+    metaDataImpl()->setId(id);
+    partContainerImpl()->setId(id);
+}
+
+/*! \reimp */
+void QMailMessage::setFrom(const QMailAddress &from)
+{
+    metaDataImpl()->setFrom(from.toString());
+    partContainerImpl()->setFrom(from.toString());
+}
+
+/*! \reimp */
+void QMailMessage::setSubject(const QString &subject)
+{
+    metaDataImpl()->setSubject(subject);
+    partContainerImpl()->setSubject(subject);
+}
+
+/*! \reimp */
+void QMailMessage::setDate(const QMailTimeStamp &timeStamp)
+{
+    metaDataImpl()->setDate(timeStamp);
+    partContainerImpl()->setDate(timeStamp);
+}
+
+/*! \reimp */
+void QMailMessage::setTo(const QList<QMailAddress>& toList)
+{
+    QString flattened(QMailAddress::toStringList(toList).join(","));
+    metaDataImpl()->setTo(flattened);
+    partContainerImpl()->setTo(flattened);
+}
+
+/*! \reimp */
+void QMailMessage::setTo(const QMailAddress& address)
+{
+    setTo(QList<QMailAddress>() << address);
+}
+
+/*!
+    Returns a list of all the cc (carbon copy) recipients specified for the message.
+
+    \sa to(), bcc(), QMailAddress
+*/  
+QList<QMailAddress> QMailMessage::cc() const
+{
+    return QMailAddress::fromStringList(headerFieldText("Cc"));
+}
+
+/*!
+    Set the list of cc (carbon copy) recipients for the message to \a ccList.
+
+    \sa setTo(), setBcc()
+*/  
+void QMailMessage::setCc(const QList<QMailAddress>& ccList)
+{
+    partContainerImpl()->setCc(QMailAddress::toStringList(ccList).join(","));
+}
+
+/*!
+    Returns a list of all the bcc (blind carbon copy) recipients specified for the message.
+
+    \sa to(), cc(), QMailAddress
+*/  
+QList<QMailAddress> QMailMessage::bcc() const
+{
+    return QMailAddress::fromStringList(headerFieldText("Bcc"));
+}
+
+/*!
+    Set the list of bcc (blind carbon copy) recipients for the message to \a bccList.
+
+    \sa setTo(), setCc()
+*/  
+void QMailMessage::setBcc(const QList<QMailAddress>& bccList)
+{
+    partContainerImpl()->setBcc(QMailAddress::toStringList(bccList).join(","));
+}
+
+/*!
+    Returns the address specified by the RFC 2822 'Reply-To' field for the message, if present.
+*/  
+QMailAddress QMailMessage::replyTo() const
+{
+    return QMailAddress(headerFieldText("Reply-To"));
+}
+
+/*!
+    Sets the RFC 2822 'Reply-To' address of the message to \a address.
+*/  
+void QMailMessage::setReplyTo(const QMailAddress &address)
+{
+    partContainerImpl()->setReplyTo(address.toString());
+}
+
+/*!
+    Returns the message ID specified by the RFC 2822 'In-Reply-To' field for the message, if present.
+*/  
+QString QMailMessage::inReplyTo() const
+{
+    return headerFieldText("In-Reply-To");
+}
+
+/*!
+    Sets the RFC 2822 'In-Reply-To' field for the message to \a messageId.
+*/
+void QMailMessage::setInReplyTo(const QString &messageId)
+{
+    partContainerImpl()->setInReplyTo(messageId);
+}
+
+/*!
+    Returns a list of all the recipients specified for the message, either as To, CC, or BCC addresses.
+
+    \sa to(), cc(), bcc(), hasRecipients()
+*/  
+QList<QMailAddress> QMailMessage::recipients() const
+{
+    QList<QMailAddress> addresses;
+
+    QStringList list;
+    list.append( headerFieldText("To").trimmed() );
+    list.append( headerFieldText("Cc").trimmed() );
+    list.append( headerFieldText("Bcc").trimmed() );
+    if (!list.isEmpty()) {
+        list.removeAll( "" );
+        list.removeAll( QString::null );
+    }
+    if (!list.isEmpty()) {
+        addresses += QMailAddress::fromStringList( list.join(",") );
+    }
+
+    return addresses;
+}
+
+/*!
+    Returns true if there are any recipients (either To, CC or BCC addresses) 
+    defined for this message; otherwise returns false.
+*/  
+bool QMailMessage::hasRecipients() const
+{
+    return partContainerImpl()->hasRecipients();
+}
+
+/*! \reimp */  
+uint QMailMessage::indicativeSize() const
+{
+    // Count the message header as one size unit
+    return partContainerImpl()->indicativeSize() + 1;
+}
+
+/*!
+    Returns the size of the message content excluding any meta data, in bytes.
+*/  
+uint QMailMessage::contentSize() const
+{
+    return customField("qtopiamail-content-size").toUInt();
+}
+
+/*!
+    Sets the size of the message content excluding any meta data to \a size, in bytes.
+*/
+void QMailMessage::setContentSize(uint size)
+{
+    setCustomField("qtopiamail-content-size", QString::number(size));
+}
+
+/*!
+    Returns a value by which the external location of the message can be referenced, if available.
+*/  
+QString QMailMessage::externalLocationReference() const
+{
+    return customField("qtopiamail-external-location-reference");
+}
+
+/*!
+    Returns the value by which the external location of the message can be referenced to \a location.
+*/
+void QMailMessage::setExternalLocationReference(const QString &location)
+{
+    setCustomField("qtopiamail-external-location-reference", location);
+}
+
+/*! \reimp */
+bool QMailMessage::contentAvailable() const
+{
+    return QMailMessageMetaData::contentAvailable();
+}
+
+/*! \reimp */
+bool QMailMessage::partialContentAvailable() const
+{
+    return QMailMessageMetaData::partialContentAvailable();
+}
+
+// The QMMMetaData half of this object is implemented in a QMailMessageMetaDataPrivate object
+/*! \internal */
+QMailMessageMetaDataPrivate* QMailMessage::metaDataImpl() 
+{ 
+    return QMailMessageMetaData::impl<QMailMessageMetaDataPrivate>(); 
+}
+
+/*! \internal */
+const QMailMessageMetaDataPrivate* QMailMessage::metaDataImpl() const 
+{ 
+    return QMailMessageMetaData::impl<const QMailMessageMetaDataPrivate>(); 
+}
+
+// The QMMPartContainer half of this object is implemented in a QMailMessagePrivate object
+/*! \internal */
+QMailMessagePrivate* QMailMessage::partContainerImpl() 
+{ 
+    return QMailMessagePartContainer::impl<QMailMessagePrivate>(); 
+}
+
+/*! \internal */
+const QMailMessagePrivate* QMailMessage::partContainerImpl() const 
+{ 
+    return QMailMessagePartContainer::impl<const QMailMessagePrivate>(); 
+}
+    
+/*! \internal */
+bool QMailMessage::contentModified() const
+{
+    return partContainerImpl()->contentModified();
+}
+
+/*! \internal */
+void QMailMessage::setUnmodified()
+{
+    metaDataImpl()->setUnmodified();
+    partContainerImpl()->setUnmodified();
+}
+
+/*! \internal */
+void QMailMessage::setHeader(const QMailMessageHeader& partHeader, const QMailMessagePartContainerPrivate* parent)
+{
+    QMailMessagePartContainer::setHeader(partHeader, parent);
+
+    // See if any of the header fields need to be propagated to the meta data object
+    foreach (const QMailMessageHeaderField& field, headerFields()) {
+        QByteArray duplicatedId(duplicatedData(field.id()));
+        if (!duplicatedId.isNull()) {
+            updateMetaData(duplicatedId, field.decodedContent());
+        }
+    }
+}
+
+/*! \internal */
+QByteArray QMailMessage::duplicatedData(const QString& id) const
+{
+    // These items are duplicated in both the message content and the meta data
+    QByteArray plainId( to7BitAscii(id).trimmed().toLower() );
+
+    if ((plainId == "from") || (plainId == "to") || (plainId == "subject") || (plainId == "date"))
+        return plainId;
+
+    return QByteArray();
+}
+
+/*! \internal */
+void QMailMessage::updateMetaData(const QByteArray& id, const QString& value)
+{
+    if (id == "from") {
+        metaDataImpl()->setFrom(value);
+    } else if (id == "to") {
+        metaDataImpl()->setTo(value);
+    } else if (id == "subject") {
+        metaDataImpl()->setSubject(value);
+    } else if (id == "date") {
+        metaDataImpl()->setDate(QMailTimeStamp(value));
+    }
+}
+
+/*! \internal */
+QMailMessage QMailMessage::fromRfc2822(LongString& ls)
+{
+    const QByteArray terminator((QByteArray(QMailMessage::CRLF) + QMailMessage::CRLF));
+
+    QMailMessage mail;
+
+    int pos = ls.indexOf(terminator);
+    if (pos == -1) {
+        // No body? Parse entirety as header
+        mail.setHeader( QMailMessageHeader( ls.toQByteArray() ) );
+    } else {
+        // Parse the header part to know what we've got
+        mail.setHeader( QMailMessageHeader( ls.left(pos).toQByteArray() ) );
+
+        // Parse the remainder as content
+        mail.partContainerImpl()->fromRfc2822( ls.mid(pos + 4) );
+    }
+
+    return mail;
+}
+
+/*! 
+    \fn QMailMessage::serialize(Stream&) const
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessage::serialize(Stream &stream) const
+{
+    metaDataImpl()->serialize(stream);
+    partContainerImpl()->serialize(stream);
+}
+
+template void QMailMessage::serialize(QDataStream &) const;
+
+/*! 
+    \fn QMailMessage::deserialize(Stream&)
+    \internal 
+*/
+template <typename Stream> 
+void QMailMessage::deserialize(Stream &stream)
+{
+    metaDataImpl()->deserialize(stream);
+    partContainerImpl()->deserialize(stream);
+}
+
+template void QMailMessage::deserialize(QDataStream &);
+