--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/messaging/win32wce/qmailcodec.cpp Wed Aug 25 15:49:42 2010 +0300
@@ -0,0 +1,1141 @@
+/****************************************************************************
+**
+** 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 "qmailcodec.h"
+#include "qmaillog.h"
+#include <QIODevice>
+#include <QTextCodec>
+#include <QtDebug>
+#include <ctype.h>
+
+// Allow these values to be reduced from test harness code:
+int QTOPIAMAIL_EXPORT MaxCharacters = QMailCodec::ChunkCharacters;
+// Must be an even multiple of 4:
+int QTOPIAMAIL_EXPORT Base64MaxLineLength = 76;
+// Can be any number:
+int QTOPIAMAIL_EXPORT QuotedPrintableMaxLineLength = 74;
+
+
+/*!
+ \class QMailCodec
+
+ \brief The QMailCodec class provides mechanisms for encoding and decoding between 7-bit ASCII strings
+ and arbitrary octet sequences.
+
+ \ingroup messaginglibrary
+
+ Messages transferred via the SMTP protocol must be encoded in 7-bit ASCII characters, even though
+ their contents are typically composed in sequences of 8-bit octets. The QMailCodec class provides
+ an interface through which data can be easily converted between an 8-bit octet sequence and
+ a 7-bit ASCII character sequence.
+
+ QMailCodec is an abstract class; in order to perform a coding operation, a derived class
+ must be used that provides a policy for mapping 8-bit data to and from 7-bit ASCII characters.
+ This policy is implemented by overriding the encodeChunk() and decodeChunk() virtual functions.
+
+ Using the QMailCodec interface, data can be encoded or decoded from an input QDataStream to an
+ output QDataStream, or for convenience, from an input QByteArray to an output QByteArray.
+
+ If the data to be encoded is in unicode form, then the QMailCodec interface can be used to
+ convert the data to ASCII via an intermediate QTextCodec, which converts the incoming text
+ to a sequence of octets. The QTextCodec used is specified by the name of the encoding
+ produced, or that decoded when decoding an ASCII input sequence. QMailCodec provides functions
+ to encode from a QTextStream to a QDataStream, and to decode from a QDataStream to a QTextStream.
+ For convenience, it is also possible to encode a QString to a QByteArray, and to decode a
+ QByteArray to a QString.
+
+ \sa QDataStream, QTextStream, QTextCodec
+*/
+
+/*!
+ \fn void QMailCodec::encodeChunk(QDataStream& out, const unsigned char* input, int length, bool finalChunk)
+
+ Overridden by derived classes to perform an encoding operation. The implementation function
+ must encode \a length 8-bit octets at the location \a input, writing the resulting ASCII characters
+ to the stream \a out. If \a finalChunk is false, further calls will be made to encodeChunk()
+ with continued input data. Otherwise, the encoding operation is complete.
+*/
+
+/*!
+ \fn void QMailCodec::decodeChunk(QDataStream& out, const char* input, int length, bool finalChunk)
+
+ Overridden by derived classes to perform a decoding operation. The implementation function
+ must decode \a length ASCII characters at the location \a input, writing the resulting octets
+ to the stream \a out. If \a finalChunk is false, further calls will be made to decodeChunk()
+ with continued input data. Otherwise, the decoding operation is complete.
+*/
+
+/*!
+ Destroys a QMailCodec instance.
+*/
+QMailCodec::~QMailCodec()
+{
+}
+
+/*!
+ \fn QMailCodec::name() const
+
+ Returns a string that identifies the subclass of QMailCodec that this instance belongs to.
+*/
+
+static void enumerateCodecs()
+{
+ static bool enumerated = false;
+
+ if (!enumerated)
+ {
+ qWarning() << "Available codecs:";
+ foreach (const QByteArray& codec, QTextCodec::availableCodecs())
+ qWarning() << " " << codec;
+
+ enumerated = true;
+ }
+}
+
+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);
+ }
+
+ QTextCodec* codec = QTextCodec::codecForName(encoding);
+ if (!codec)
+ {
+ qWarning() << "QMailCodec::codecForName - Unable to find codec for charset" << encoding;
+ enumerateCodecs();
+ }
+
+ return codec;
+ }
+
+ return 0;
+}
+
+/*!
+ Writes the data read from the stream \a in to the stream \a out, as a sequence
+ of 7-bit ASCII characters. The unicode characters read from \a in are first
+ encoded to the text encoding \a charset.
+
+ \sa QTextCodec::codecForName()
+*/
+void QMailCodec::encode(QDataStream& out, QTextStream& in, const QString& charset)
+{
+ if (QTextCodec* codec = codecForName(charset.toLatin1()))
+ {
+ while (!in.atEnd())
+ {
+ QString chunk = in.read(MaxCharacters);
+ QByteArray charsetEncoded = codec->fromUnicode(chunk);
+
+ encodeChunk(out,
+ reinterpret_cast<const unsigned char*>(charsetEncoded.constData()),
+ charsetEncoded.length(),
+ in.atEnd());
+ }
+ }
+}
+
+/*!
+ Writes the data read from the stream \a in to the stream \a out, converting from
+ a sequence of 7-bit ASCII characters. The characters read from \a in are
+ decoded from the text encoding \a charset to unicode.
+
+ \sa QTextCodec::codecForName()
+*/
+void QMailCodec::decode(QTextStream& out, QDataStream& in, const QString& charset)
+{
+ if (QTextCodec* codec = codecForName(charset.toLatin1()))
+ {
+ QByteArray decoded;
+ {
+ QDataStream decodedStream(&decoded, QIODevice::WriteOnly);
+
+ char* buffer = new char[MaxCharacters];
+ while (!in.atEnd())
+ {
+ int length = in.readRawData(buffer, MaxCharacters);
+
+ // Allow for decoded data to be twice the size without reallocation
+ decoded.reserve(decoded.size() + (MaxCharacters * 2));
+
+ decodeChunk(decodedStream, buffer, length, in.atEnd());
+ }
+ delete [] buffer;
+ }
+
+ // This is an unfortunately-necessary copy operation; we should investigate
+ // modifying QTextCodec to support a stream interface
+ QString unicode = codec->toUnicode(decoded);
+ out << unicode;
+ out.flush();
+ }
+}
+
+/*!
+ Writes the data read from the stream \a in to the stream \a out, as a sequence
+ of 7-bit ASCII characters.
+*/
+void QMailCodec::encode(QDataStream& out, QDataStream& in)
+{
+ char* buffer = new char[MaxCharacters];
+ while (!in.atEnd())
+ {
+ int length = in.readRawData(buffer, MaxCharacters);
+
+ encodeChunk(out, reinterpret_cast<unsigned char*>(buffer), length, in.atEnd());
+ }
+ delete [] buffer;
+}
+
+/*!
+ Writes the data read from the stream \a in to the stream \a out, converting from
+ a sequence of 7-bit ASCII characters.
+*/
+void QMailCodec::decode(QDataStream& out, QDataStream& in)
+{
+ char* buffer = new char[MaxCharacters];
+ while (!in.atEnd())
+ {
+ int length = in.readRawData(buffer, MaxCharacters);
+
+ decodeChunk(out, buffer, length, in.atEnd());
+ }
+ delete [] buffer;
+}
+
+/*!
+ Writes the data read from the stream \a in to the stream \a out, without conversion.
+*/
+void QMailCodec::copy(QDataStream& out, QDataStream& in)
+{
+ char* buffer = new char[MaxCharacters];
+ while (!in.atEnd())
+ {
+ int length = in.readRawData(buffer, MaxCharacters);
+ out.writeRawData(buffer, length);
+ }
+ delete [] buffer;
+}
+
+/*!
+ Writes the data read from the stream \a in to the stream \a out, without conversion.
+*/
+void QMailCodec::copy(QTextStream& out, QTextStream& in)
+{
+ while (!in.atEnd())
+ {
+ QString input = in.read(MaxCharacters);
+ out << input;
+ }
+}
+
+/*!
+ Returns a QByteArray containing the string \a input, encoded to the text encoding \a charset
+ and then to a sequence of 7-bit ASCII characters.
+
+ \sa QTextCodec::codecForName()
+*/
+QByteArray QMailCodec::encode(const QString& input, const QString& charset)
+{
+ QByteArray result;
+ {
+ QDataStream out(&result, QIODevice::WriteOnly);
+
+ // We can't currently guarantee that this is safe - we should investigate modifying
+ // QTextStream to support a read-only interface...
+ QTextStream in(const_cast<QString*>(&input), QIODevice::ReadOnly);
+
+ encode(out, in, charset);
+ }
+
+ return result;
+}
+
+/*!
+ Returns a QString containing characters decoded from the text encoding \a charset, which
+ are decoded from the sequence of 7-bit ASCII characters read from \a input.
+
+ \sa QTextCodec::codecForName()
+*/
+QString QMailCodec::decode(const QByteArray& input, const QString& charset)
+{
+ QString result;
+ {
+ QTextStream out(&result, QIODevice::WriteOnly);
+ QDataStream in(input);
+ decode(out, in, charset);
+ }
+
+ return result;
+}
+
+/*!
+ Returns a QByteArray containing the octets from \a input, encoded to a sequence of
+ 7-bit ASCII characters.
+*/
+QByteArray QMailCodec::encode(const QByteArray& input)
+{
+ QByteArray result;
+ {
+ QDataStream out(&result, QIODevice::WriteOnly);
+ QDataStream in(input);
+
+ encode(out, in);
+ }
+
+ return result;
+}
+
+/*!
+ Returns a QByteArray containing the octets decoded from the sequence of 7-bit ASCII
+ characters in \a input.
+*/
+QByteArray QMailCodec::decode(const QByteArray& input)
+{
+ QByteArray result;
+ {
+ QDataStream out(&result, QIODevice::WriteOnly);
+ QDataStream in(input);
+
+ decode(out, in);
+ }
+
+ return result;
+}
+
+
+// ASCII character values used throughout
+const unsigned char MinPrintableRange = 0x20;
+const unsigned char MaxPrintableRange = 0x7e;
+const unsigned char HorizontalTab = 0x09;
+const unsigned char LineFeed = 0x0a;
+const unsigned char FormFeed = 0x0c;
+const unsigned char CarriageReturn = 0x0d;
+const unsigned char Space = 0x20;
+const unsigned char Equals = 0x3d;
+const unsigned char ExclamationMark = 0x21;
+const unsigned char Asterisk = 0x2a;
+const unsigned char Plus = 0x2b;
+const unsigned char Minus = 0x2d;
+const unsigned char Slash = 0x2f;
+const unsigned char Underscore = 0x5f;
+
+// Static data and functions for Base 64 codec
+static const char Base64Characters[64 + 1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const unsigned char* Base64Values = reinterpret_cast<const unsigned char*>(Base64Characters);
+static const unsigned char Base64PaddingByte = 0x3d;
+
+static inline unsigned char base64Index(const char ascii)
+{
+ if (ascii >= 'A' && ascii <= 'Z')
+ return (ascii - 'A');
+ if (ascii >= 'a' && ascii <= 'z')
+ return (ascii - 'a') + 26;
+ if (ascii >= '0' && ascii <= '9')
+ return (ascii - '0') + 52;
+ if (ascii == '+')
+ return 62;
+ if (ascii == '/')
+ return 63;
+ if (ascii == Base64PaddingByte)
+ return 64;
+ return 65;
+}
+
+
+/*!
+ \class QMailBase64Codec
+
+ \brief The QMailBase64Codec class encodes or decodes between 8-bit data and 7-bit ASCII, using the Base64
+ character mapping scheme.
+
+ \ingroup messaginglibrary
+
+ The Base64 character mapping scheme maps arbitrary 8-bit values into a range of 64 printable
+ characters from the 7-bit ASCII set. The mapping scheme used is defined in
+ \l{http://www.ietf.org/rfc/rfc2045.txt} {RFC 2045} (Multipurpose Internet Mail Extensions Part One).
+ This encoding is also defined as the '"B" encoding' for 'encoded words' in
+ \l{http://www.ietf.org/rfc/rfc2047.txt} {RFC 2047} (Multipurpose Internet Mail Extensions Part Three).
+
+ The Base64 scheme encodes every incoming octet regardless of its original value, and thus
+ produces the same ratio of output length to input length for any input data sequence. Since
+ Base64 encodes four output characters for every three input octets, it produces a 33%
+ increase in stream size when encoding.
+
+ An instance of QMailBase64Codec contains state information about the encoding or decoding
+ operation it performs, so an instance should be used for a single coding operation only:
+
+ \code
+ QString textData = acquireInput();
+
+ // Encode the string data to a UTF-8 byte sequence, and then encode to Base64
+ QMailBase64Codec encoder;
+ QByteArray base64Data = encoder.encode(textData, "UTF-8");
+ \endcode
+
+ \sa QMailCodec
+*/
+
+/*!
+ \enum QMailBase64Codec::ContentType
+
+ This enumerated type is used to specify whether content is textual data or binary data.
+
+ \value Text The data is textual data; newline sequences within the data will be converted during coding.
+ \value Binary The data is not textual, and does not contain newline sequences.
+*/
+
+/*!
+ Constructs a codec object for coding to or from Base64 encoding.
+
+ If \a content is QMailBase64Codec::Text, then newline sequences will be converted
+ between the local representation (for example, 0x0A on Unix) and the transmission standard
+ representation (0x0D 0x0A). Otherwise, the data will be coded without modification.
+
+ The maximum number of encoded characters per output line can be specified as \a maximumLineLength.
+ If not specified, or specified to a non-positive value, a default value will be used.
+*/
+QMailBase64Codec::QMailBase64Codec(ContentType content, int maximumLineLength)
+ : _content(content),
+ _maximumLineLength(maximumLineLength),
+ _lastChar(0)
+{
+ // Our buffer output iterators - initially at start of buffer
+ _encodeBufferOut = _encodeBuffer;
+ _decodeBufferOut = _decodeBuffer;
+
+ // Each encoded line will contain 76 output chars - 57 input chars
+ if (_maximumLineLength <= 0)
+ _maximumLineLength = Base64MaxLineLength;
+ _encodeLineCharsRemaining = (_maximumLineLength / 4 * 3);
+
+ // Count the number of padding characters encountered during decode
+ _decodePaddingCount = 0;
+}
+
+/*! \reimp */
+QString QMailBase64Codec::name() const
+{
+ return "QMailBase64Codec";
+}
+
+/*! \internal */
+void QMailBase64Codec::encodeChunk(QDataStream& out, const unsigned char* it, int length, bool finalChunk)
+{
+ unsigned char* bufferEnd = _encodeBuffer + 3;
+
+ // Set the input pointers relative to this input
+ const unsigned char* lineEnd = it + _encodeLineCharsRemaining;
+ const unsigned char* const end = it + length;
+
+ while (it != end)
+ {
+ bool trailingLF = false;
+
+ const unsigned char input = *it++;
+ if ((input == CarriageReturn || input == LineFeed) && (_content == Text))
+ {
+ if (_lastChar == CarriageReturn && input == LineFeed)
+ {
+ // We have already encoded this character-sequence
+
+ // We can accept one more input character than accounted for
+ lineEnd += 1;
+ }
+ else
+ {
+ // We must replace this character with ASCII CRLF
+ *_encodeBufferOut++ = CarriageReturn;
+ if (_encodeBufferOut != bufferEnd)
+ {
+ *_encodeBufferOut++ = LineFeed;
+ }
+ else
+ {
+ trailingLF = true;
+ }
+
+ // We can accept one fewer input character than expected, now
+ lineEnd -= 1;
+ }
+
+ _lastChar = input;
+ }
+ else
+ *_encodeBufferOut++ = input;
+
+ if (_encodeBufferOut == bufferEnd)
+ {
+ // We have buffered 3 input bytes - write them out as four output bytes
+ out << Base64Values[(_encodeBuffer[0] >> 2) & 0x3f];
+ out << Base64Values[(((_encodeBuffer[0] & 0x03) << 4) | (_encodeBuffer[1] >> 4)) & 0x3f];
+ out << Base64Values[(((_encodeBuffer[1] & 0x0f) << 2) | (_encodeBuffer[2] >> 6)) & 0x3f];
+ out << Base64Values[_encodeBuffer[2] & 0x3f];
+
+ _encodeBufferOut = _encodeBuffer;
+ if ((it >= lineEnd) && ((it != end) || !finalChunk))
+ {
+ // Insert an ASCII CRLF sequence
+ out << static_cast<unsigned char>(CarriageReturn) << static_cast<unsigned char>(LineFeed);
+ lineEnd += (_maximumLineLength / 4 * 3);
+ }
+ }
+
+ if (trailingLF)
+ {
+ *_encodeBufferOut++ = LineFeed;
+ }
+ }
+
+ if (finalChunk)
+ {
+ int bufferedBytesRemaining = _encodeBufferOut - _encodeBuffer;
+ if (bufferedBytesRemaining > 0)
+ {
+ // We have some data still buffered - pad buffer with zero bits
+ *_encodeBufferOut = 0;
+
+ out << Base64Values[(_encodeBuffer[0] >> 2) & 0x3f];
+ out << Base64Values[(((_encodeBuffer[0] & 0x03) << 4) | (_encodeBuffer[1] >> 4)) & 0x3f];
+
+ // Indicate unused bytes with the padding character
+ if (bufferedBytesRemaining == 1)
+ {
+ out << Base64PaddingByte;
+ out << Base64PaddingByte;
+ }
+ else // must be two
+ {
+ out << Base64Values[(((_encodeBuffer[1] & 0x0f) << 2) | (_encodeBuffer[2] >> 6)) & 0x3f];
+ out << Base64PaddingByte;
+ }
+ }
+ }
+ else
+ {
+ // Leave the buffer intact, and adjust the line char count
+ _encodeLineCharsRemaining = (lineEnd - it);
+ }
+}
+
+/*! \internal */
+void QMailBase64Codec::decodeChunk(QDataStream& out, const char* it, int length, bool finalChunk)
+{
+ unsigned char* bufferEnd = _decodeBuffer + 4;
+
+ const char* const end = it + length;
+ while (it != end)
+ {
+ // Convert each character to the index value
+ *_decodeBufferOut = base64Index(*it++);
+ if (*_decodeBufferOut == 64)
+ ++_decodePaddingCount;
+ if (*_decodeBufferOut <= 64)
+ ++_decodeBufferOut;
+
+ if (_decodeBufferOut == bufferEnd)
+ {
+ // We have buffered 4 input characters - write them out as three output bytes
+ // unless some of them are padding
+
+ unsigned char decoded[3] = { 0 };
+ decoded[0] = static_cast<unsigned char>((_decodeBuffer[0] << 2) | ((_decodeBuffer[1] >> 4) & 0x03));
+ decoded[1] = static_cast<unsigned char>((_decodeBuffer[1] << 4) | ((_decodeBuffer[2] >> 2) & 0x0f));
+ decoded[2] = static_cast<unsigned char>(((_decodeBuffer[2] & 0x03) << 6) | (_decodeBuffer[3] & 0x3f));
+
+ int remainingChars = (3 - _decodePaddingCount);
+ for (int i = 0; i < remainingChars; ++i)
+ {
+ if ((decoded[i] == CarriageReturn || decoded[i] == LineFeed) && (_content == Text))
+ {
+ if (_lastChar == CarriageReturn && decoded[i] == LineFeed)
+ {
+ // We have already processed this sequence
+ }
+ else
+ {
+ // We should output the local newline sequence, but we can't
+ // because we don't know what it is, and C++ translation-from-\n will
+ // only work if the stream is a file...
+ out << static_cast<unsigned char>('\n');
+ }
+
+ _lastChar = decoded[i];
+ }
+ else
+ out << decoded[i];
+ }
+
+ _decodeBufferOut = _decodeBuffer;
+ }
+ }
+
+ if (finalChunk)
+ {
+ // There should always be an even multiple of 4 input bytes
+ int bufferedBytesRemaining = _decodeBufferOut - _decodeBuffer;
+ if (bufferedBytesRemaining > 0)
+ {
+ qWarning() << "Huh? bytes remaining:" << bufferedBytesRemaining;
+ }
+ }
+}
+
+
+// Static data and functions for Quoted-Prinatable codec
+static const unsigned char NilPreceding = 0x7f;
+static const char QuotedPrintableCharacters[16 + 1] = "0123456789ABCDEF";
+static const unsigned char* QuotedPrintableValues = reinterpret_cast<const unsigned char*>(QuotedPrintableCharacters);
+
+static bool requiresEscape(unsigned char input, QMailQuotedPrintableCodec::ConformanceType conformance, int charsRemaining)
+{
+ // For both, we need to escape '=' and anything unprintable
+ bool escape = ((input > MaxPrintableRange) ||
+ ((input < MinPrintableRange) && (input != HorizontalTab) && (input != FormFeed)) ||
+ (input == Equals));
+
+ // For RFC 2047, we need to escape '?', '_', ' ' & '\t'
+ // In fact, since the output may be used in a header field 'word', then the only characters
+ // that can be used un-escaped are: alphanumerics, '!', '*', '+' '-', '/' and '_'
+ if (!escape && (conformance == QMailQuotedPrintableCodec::Rfc2047))
+ {
+ // We can also ignore space, since it will become an underscore
+ if ((input != ExclamationMark) && (input != Asterisk) && (input != Plus) &&
+ (input != Minus) && (input != Slash) && (input != Underscore) && (input != Space))
+ {
+ escape = !isalnum(input);
+ }
+ }
+
+ if (!escape && (input == HorizontalTab || input == Space))
+ {
+ // The (potentially) last whitespace character on a line must be escaped
+ if (charsRemaining <= 3)
+ escape = true;
+ }
+
+ return escape;
+}
+
+static inline void encodeCharacter(QDataStream& out, unsigned char value)
+{
+ out << static_cast<unsigned char>(Equals);
+ out << QuotedPrintableValues[value >> 4];
+ out << QuotedPrintableValues[value & 0x0f];
+}
+
+static inline void lineBreak(QDataStream& out, int* _encodeLineCharsRemaining, int maximumLineLength)
+{
+ out << static_cast<unsigned char>(Equals);
+ out << static_cast<unsigned char>(LineFeed);
+
+ *_encodeLineCharsRemaining = maximumLineLength;
+}
+
+static inline unsigned char decodeCharacter(unsigned char value)
+{
+ if ((value >= 0x30) && (value <= 0x39))
+ return (value - 0x30);
+
+ if ((value >= 0x41) && (value <= 0x46))
+ return ((value - 0x41) + 10);
+
+ if ((value >= 0x61) && (value <= 0x66))
+ return ((value - 0x61) + 10);
+
+ return 0;
+}
+
+
+/*!
+ \class QMailQuotedPrintableCodec
+
+ \brief The QMailQuotedPrintableCodec class encodes or decodes between 8-bit data and 7-bit ASCII,
+ using the 'quoted printable' character mapping scheme.
+
+ \ingroup messaginglibrary
+
+ The 'quoted printable' character mapping scheme maps arbitrary 8-bit values into 7-bit ASCII
+ characters, by replacing values that cannot be directly represented with an escape sequence.
+ The mapping scheme used is defined in
+ \l{http://www.ietf.org/rfc/rfc2045.txt} {RFC 2045} (Multipurpose Internet Mail Extensions Part One).
+ A minor variation on the scheme is defined as the '"Q" encoding' for 'encoded words' in
+ \l{http://www.ietf.org/rfc/rfc2047.txt} {RFC 2047} (Multipurpose Internet Mail Extensions Part Three).
+
+ The 'quoted printable' scheme encodes only those incoming octet values that cannot be directly
+ represented in ASCII, by replacing the input octet with a three-character sequence that encodes
+ the numeric value of the original octet. Therefore, the ratio of input length to output length
+ for any input data sequence depends on the percentage of the input that corresponds to ASCII
+ values, with ASCII-like encodings producing only small increases. With an input data encoding
+ such as Latin-1 (ISO-8859-1), the output maintains a reasonable degree of human-readability.
+
+ An instance of QMailQuotedPrintableCodec contains state information about the encoding or decoding
+ operation it performs, so an instance should be used for a single coding operation only:
+
+ \code
+ QByteArray asciiData = acquireInput();
+
+ // We know the data is text in Latin-1 encoding, so decode the data from
+ // quoted printable ASCII encoding, and then decode from Latin-1 to unicode
+ QMailQuotedPrintableCodec decoder(QMailQuotedPrintableCodec::Text, QMailQuotedPrintableCodec::Rfc2045);
+ QString textData = decoder.decode(asciiData, "ISO-8859-1");
+ \endcode
+
+ \sa QMailCodec
+*/
+
+/*!
+ \enum QMailQuotedPrintableCodec::ContentType
+
+ This enumerated type is used to specify whether content is textual data or binary data.
+
+ \value Text The data is textual data; newline sequences within the data will be converted during coding.
+ \value Binary The data is not textual, and does not contain newline sequences.
+*/
+
+/*!
+ \enum QMailQuotedPrintableCodec::ConformanceType
+
+ This enumerated type is used to specify which RFC the coding operation should conform to.
+
+ \value Rfc2045 The coding should be performed according to the requirements of RFC 2045.
+ \value Rfc2047 The coding should be performed according to the requirements of RFC 2047's '"Q" encoding'.
+*/
+
+/*!
+ Constructs a codec object for coding data of type \a content, using the mapping scheme
+ specified by the requirements of \a conformance.
+
+ If \a content is QMailQuotedPrintableCodec::Text, then newline sequences will be converted
+ between the local representation (for example, 0x0A on Unix) and the transmission standard
+ representation (0x0D 0x0A). Otherwise, the data will be coded without modification.
+
+ If \a conformance is QMailQuotedPrintableCodec::Rfc2047, then coding will use the mapping
+ scheme of the
+ \l{http://www.ietf.org/rfc/rfc2047.txt} {RFC 2047} '"Q" encoding'; otherwise the scheme defined in
+ \l{http://www.ietf.org/rfc/rfc2045.txt} {RFC 2045} will be used.
+
+ The maximum number of encoded output characters per line can be specified as \a maximumLineLength.
+ If not specified, or specified to a non-positive value, a default value will be used.
+*/
+QMailQuotedPrintableCodec::QMailQuotedPrintableCodec(ContentType content, ConformanceType conformance, int maximumLineLength)
+ : _content(content),
+ _conformance(conformance),
+ _maximumLineLength(maximumLineLength)
+{
+ // We're allowed up to 76 chars per output line, but the RFC isn't really clear on
+ // whether this includes the '=' and '\n' of a soft line break, so we'll assume they're counted
+ if (_maximumLineLength <= 0)
+ _maximumLineLength = QuotedPrintableMaxLineLength;
+
+ _encodeLineCharsRemaining = _maximumLineLength;
+ _encodeLastChar = '\0';
+
+ _decodePrecedingInput = NilPreceding;
+ _decodeLastChar = '\0';
+}
+
+/*! \reimp */
+QString QMailQuotedPrintableCodec::name() const
+{
+ return "QMailQuotedPrintableCodec";
+}
+
+/*! \internal */
+void QMailQuotedPrintableCodec::encodeChunk(QDataStream& out, const unsigned char* it, int length, bool finalChunk)
+{
+ // Set the input pointers relative to this input
+ const unsigned char* const end = it + length;
+
+ while (it != end)
+ {
+ unsigned char input = *it++;
+
+ if ((input == CarriageReturn || input == LineFeed) && (_content == Text))
+ {
+ if (_encodeLastChar == CarriageReturn && input == LineFeed)
+ {
+ // We have already encoded this character-sequence
+ }
+ else
+ {
+ // We must replace this character with ascii CRLF
+ out << CarriageReturn << LineFeed;
+ }
+
+ _encodeLastChar = input;
+ _encodeLineCharsRemaining = _maximumLineLength;
+ continue;
+ }
+
+ bool escape = requiresEscape(input, _conformance, _encodeLineCharsRemaining);
+ int charsRequired = (escape ? 3 : 1);
+
+ // If we can't fit this character on the line, insert a line break
+ if (charsRequired > _encodeLineCharsRemaining)
+ {
+ lineBreak(out, &_encodeLineCharsRemaining, _maximumLineLength);
+
+ // We may no longer need the encoding after the line break
+ if (input == Space || (input == HorizontalTab && _conformance != Rfc2047))
+ charsRequired = 1;
+ }
+
+ if (charsRequired == 1)
+ {
+ if (input == Space && _conformance == Rfc2047) // output space as '_'
+ out << static_cast<unsigned char>(Underscore);
+ else
+ out << input;
+ }
+ else
+ encodeCharacter(out, input);
+
+ _encodeLineCharsRemaining -= charsRequired;
+
+ if ((_encodeLineCharsRemaining == 0) && !(finalChunk && (it == end)))
+ lineBreak(out, &_encodeLineCharsRemaining, _maximumLineLength);
+
+ _encodeLastChar = input;
+ }
+
+ Q_UNUSED(finalChunk)
+}
+
+/*! \internal */
+void QMailQuotedPrintableCodec::decodeChunk(QDataStream& out, const char* it, int length, bool finalChunk)
+{
+ const char* const end = it + length;
+
+ // The variable _decodePrecedingInput holds any unprocessed input from a previous call:
+ // If '=', we've parsed only that char, otherwise, it is the hex value of the first parsed character
+ if ((_decodePrecedingInput != NilPreceding) && (it != end))
+ {
+ unsigned char value = 0;
+ if (_decodePrecedingInput == Equals)
+ {
+ // Get the first escaped char
+ unsigned char input = *it++;
+ if (input == LineFeed || input == CarriageReturn)
+ {
+ // This is only a soft-line break
+ _decodePrecedingInput = NilPreceding;
+ }
+ else
+ {
+ value = decodeCharacter(input);
+ _decodePrecedingInput = value;
+ }
+ }
+ else
+ {
+ // We already have partial escaped input
+ value = _decodePrecedingInput;
+ }
+
+ if (it != end && _decodePrecedingInput != NilPreceding)
+ {
+ out << static_cast<unsigned char>((value << 4) | decodeCharacter(*it++));
+ _decodePrecedingInput = NilPreceding;
+ }
+ }
+
+ while (it != end)
+ {
+ unsigned char input = *it++;
+ if (input == Equals)
+ {
+ // We are in an escape sequence
+ if (it == end)
+ {
+ _decodePrecedingInput = Equals;
+ }
+ else
+ {
+ input = *it++;
+ if (input == LineFeed || input == CarriageReturn)
+ {
+ // This is a soft-line break - move on
+ }
+ else
+ {
+ // This is an encoded character
+ unsigned char value = decodeCharacter(input);
+
+ if (it == end)
+ {
+ _decodePrecedingInput = value;
+ }
+ else
+ {
+ out << static_cast<unsigned char>((value << 4) | decodeCharacter(*it++));
+ }
+ }
+ }
+ }
+ else
+ {
+ if ((input == CarriageReturn || input == LineFeed) && (_content == Text))
+ {
+ if (_decodeLastChar == CarriageReturn && input == LineFeed)
+ {
+ // We have already processed this sequence
+ }
+ else
+ {
+ // We should output the local newline sequence, but we can't
+ // because we don't know what it is, and C++ translation-from-\n will
+ // only work if the stream is a file...
+ out << static_cast<unsigned char>('\n');
+ }
+ }
+ else if (input == Underscore && _conformance == Rfc2047)
+ out << static_cast<unsigned char>(Space);
+ else
+ out << input;
+ }
+
+ _decodeLastChar = input;
+ }
+
+ if (finalChunk && _decodePrecedingInput != NilPreceding)
+ {
+ qWarning() << "Huh? unfinished escape sequence...";
+ }
+}
+
+static void writeStream(QDataStream& out, const char* it, int length)
+{
+ int totalWritten = 0;
+ while (totalWritten < length)
+ {
+ int bytesWritten = out.writeRawData(it + totalWritten, length - totalWritten);
+ if (bytesWritten == -1)
+ return;
+
+ totalWritten += bytesWritten;
+ }
+}
+
+/*!
+ \class QMailPassThroughCodec
+
+ \brief The QMailPassThroughCodec class uses the QMailCodec interface to move data between streams
+ without coding or decoding.
+
+ \ingroup messaginglibrary
+
+ The QMailPassThroughCodec allows client code to use the same QMailCodec interface to convert data between
+ different ASCII encodings, or no encoding at all, without having to be aware of the details involved.
+
+ The pass-through codec is primarily useful when communicating with SMTP servers supporting the
+ \l{http://www.ietf.org/rfc/rfc1652.txt} {RFC 1652} (8BITMIME) extension, which permits the exchange
+ of data without coding via 7-bit ASCII.
+
+ A QMailPassThroughCodec can be instantiated directly, but is more likely to be used polymorphically:
+
+ \code
+ // Get an object to perform the encoding required for the current server
+ QMailCodec* encoder = getCodecForServer(currentServer());
+
+ // If the codec returned is a QMailPassThroughCodec, the input data will
+ // be written to the output stream without encoding to 7-bit ASCII
+ encoder->encode(outputStream, inputStream);
+ \endcode
+
+ \sa QMailCodec
+*/
+
+/*! \reimp */
+QString QMailPassThroughCodec::name() const
+{
+ return "QMailPassThroughCodec";
+}
+
+/*! \internal */
+void QMailPassThroughCodec::encodeChunk(QDataStream& out, const unsigned char* it, int length, bool finalChunk)
+{
+ writeStream(out, reinterpret_cast<const char*>(it), length);
+
+ Q_UNUSED(finalChunk)
+}
+
+/*! \internal */
+void QMailPassThroughCodec::decodeChunk(QDataStream& out, const char* it, int length, bool finalChunk)
+{
+ writeStream(out, it, length);
+
+ Q_UNUSED(finalChunk)
+}
+
+
+/*!
+ \class QMailLineEndingCodec
+
+ \brief The QMailLineEndingCodec class encodes textual data to use CR/LF line endings required for SMTP transmission.
+
+ \ingroup messaginglibrary
+
+ The QMailLineEndingCodec allows client code to use the QMailCodec interface to encode textual data
+ from the local line-ending convention to the CR/LF convention required for SMTP transmission. The
+ codec will convert from single carriage return or single line feed line-endings to CR/LF pairs, or
+ will preserve data already using the correct encoding.
+
+ Decoded data will have CR/LF pairs converted to \c \n.
+
+ An instance of QMailLineEndingCodec contains state information about the encoding or decoding
+ operation it performs, so an instance should be used for a single coding operation only:
+
+ \sa QMailCodec
+*/
+
+/*!
+ Constructs a codec object for coding text data, converting between the local line-ending
+ convention and the CR/LF line-ending sequence required for SMTP transmission.
+*/
+QMailLineEndingCodec::QMailLineEndingCodec()
+ : _lastChar(0)
+{
+}
+
+/*! \reimp */
+QString QMailLineEndingCodec::name() const
+{
+ return "QMailLineEndingCodec";
+}
+
+/*! \internal */
+void QMailLineEndingCodec::encodeChunk(QDataStream& out, const unsigned char* it, int length, bool finalChunk)
+{
+ const unsigned char* const end = it + length;
+
+ const unsigned char* begin = it;
+ while (it != end)
+ {
+ const unsigned char input = *it;
+ if (input == CarriageReturn || input == LineFeed)
+ {
+ if (_lastChar == CarriageReturn && input == LineFeed)
+ {
+ // We have already encoded this character-sequence; skip the input
+ begin = (it + 1);
+ }
+ else
+ {
+ // Write the preceding characters
+ if (it > begin)
+ writeStream(out, reinterpret_cast<const char*>(begin), (it - begin));
+
+ // We must replace this character with ascii CRLF
+ out << CarriageReturn << LineFeed;
+ begin = (it + 1);
+ }
+ }
+
+ _lastChar = input;
+ ++it;
+ }
+
+ if (it > begin)
+ {
+ // Write the remaining characters
+ writeStream(out, reinterpret_cast<const char*>(begin), (it - begin));
+ }
+
+ Q_UNUSED(finalChunk)
+}
+
+/*! \internal */
+void QMailLineEndingCodec::decodeChunk(QDataStream& out, const char* it, int length, bool finalChunk)
+{
+ const char* const end = it + length;
+
+ const char* begin = it;
+ while (it != end)
+ {
+ const char input = *it;
+ if (input == CarriageReturn || input == LineFeed)
+ {
+ if (_lastChar == CarriageReturn && input == LineFeed)
+ {
+ // We have already processed this sequence
+ begin = (it + 1);
+ }
+ else
+ {
+ // Write the preceding characters
+ if (it > begin)
+ writeStream(out, begin, (it - begin));
+
+ // We should output the local newline sequence, but we can't
+ // because we don't know what it is, and C++ translation-from-\n will
+ // only work if the stream is a file...
+ out << static_cast<unsigned char>('\n');
+ begin = (it + 1);
+ }
+ }
+
+ _lastChar = input;
+ ++it;
+ }
+
+ if (it > begin)
+ {
+ // Write the remaining characters
+ writeStream(out, begin, (it - begin));
+ }
+
+ Q_UNUSED(finalChunk)
+}
+