/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Messaging Framework.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** 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 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qmailcodec.h"
#include "qmaillog.h"

#ifdef HAVE_LIBICU
#include "qcharsetdetector.h"
#endif

#include <QIODevice>
#include <QRegularExpression>
#include <QTextCodec>
#include <QtDebug>
#include <ctype.h>

// Allow these values to be reduced from test harness code:
int QMF_EXPORT MaxCharacters = QMailCodec::ChunkCharacters;
// Must be an even multiple of 4:
int QMF_EXPORT Base64MaxLineLength = 76;
// Can be any number:
int QMF_EXPORT QuotedPrintableMaxLineLength = 74;

/*!
  \class QMailCodec

  \preliminary
  \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;
    }
}

/*!
    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 QByteArray& charset)
{
    if (QTextCodec* codec = codecForName(charset))
    {
        QTextEncoder *encoder = codec->makeEncoder(QTextCodec::IgnoreHeader);
        while (!in.atEnd())
        {
            QString chunk = in.read(MaxCharacters);
            QByteArray charsetEncoded = encoder->fromUnicode(chunk);

            encodeChunk(out, 
                        reinterpret_cast<const unsigned char*>(charsetEncoded.constData()), 
                        charsetEncoded.length(),
                        in.atEnd());
        }
        delete encoder;
    }
}

/*!
    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 icharset to unicode.

    \sa QTextCodec::codecForName()
*/
void QMailCodec::decode(QTextStream& out, QDataStream& in, const QByteArray& icharset)
{
    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;
    }
    QTextCodec* codec = codecForName(icharset);
    if (!codec)
        codec = QTextCodec::codecForUtfText(decoded, codecForName("UTF-8"));

    // This is an unfortunately-necessary copy operation; we should investigate
    // using QTextCodec::makeDecoder, and adding a factory method to
    // QMailMessagePartContainer that returns a readable QIODevice*
    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);
        if (length > 0 && in.status() == QDataStream::ReadPastEnd) {
            // work around QTBUG-69474
            in.resetStatus();
        }
        decodeChunk(out, buffer, length, in.atEnd());
    }
    delete [] buffer;
}

/*!
    Returns a charset that may be better supported than the specified \a
    charset. If charset is ascii and \a translateAscii is true, then
    the Latin-1 codec will be returned.
*/
QByteArray QMailCodec::bestCompatibleCharset(const QByteArray& charset, bool translateAscii)
{
    QByteArray encoding(charset.toLower());

    if (!encoding.isEmpty()) {
        int index;

        if (translateAscii && (encoding.contains("ascii") || (encoding.contains("unicode-1-1-utf-7")))) {
            // We'll assume the text is plain ASCII, to be extracted to Latin-1
            encoding = "ISO-8859-1";
        } else if(encoding.contains("ks_c_5601")) {
            encoding = "EUC-KR";
        } else if (encoding == "gb2312" || encoding == "gbk") {
            // gb18030 is a superset of gb2312 and gbk, prefer it
            encoding = "gb18030";
        } else if (encoding == "windows-874") {
            // windows-874 is unsupported, but compatible with ibm874
            encoding = "ibm874";
        } else if ((index = encoding.indexOf('*')) != -1) {
            // This charset specification includes a trailing language specifier
            encoding = encoding.left(index);
        }
    }

    return encoding;
}

/*!
    Returns a pointer to an appropriate QTextCodec based on \a charset. If charset is ascii and \a translateAscii is true,
    then the Latin-1 codec will be returned.

    Returns 0 if a codec could not be found.
    
    \sa QMailCodec::autoDetectEncoding()
*/
QTextCodec* QMailCodec::codecForName(const QByteArray& charset, bool translateAscii)
{
    QByteArray encoding(bestCompatibleCharset(charset, translateAscii));

    if (!encoding.isEmpty()) {
        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, 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.
    \a out text buffer will be in unicode, while \a in could be in \a charset.
*/
void QMailCodec::copy(QTextStream& out, QDataStream& in, const QByteArray& charset)
{
    QTextCodec* codec = codecForName(charset);
    if (!codec)
        codec = codecForName("UTF-8");
    QTextDecoder *decoder = codec->makeDecoder();

    char* buffer = new char[MaxCharacters];
    while (!in.atEnd())
    {
        int length = in.readRawData(buffer, MaxCharacters);
        out << decoder->toUnicode(buffer, length);
    }
    delete [] buffer;

    delete decoder;
}

/*!
    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 QByteArray& 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 QByteArray& 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;
}

/*!
    Returns the charset of \a text using automatic detection; or an empty
    string if detection fails.
    
    \sa QTextCodec::codecForName()
*/
QString QMailCodec::autoDetectEncoding(const QByteArray& text)
{
#ifdef HAVE_LIBICU
    if (text.isEmpty()) return QString();
    QCharsetDetector charsetDetector;
    charsetDetector.setText(text);
    QString result(charsetDetector.detect().name());
    return result;
#else
    Q_UNUSED(text);
    return QString();
#endif
}

// 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

  \preliminary
  \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 QLatin1String("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>(CarriageReturn);
    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

  \preliminary
  \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 QLatin1String("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);
        // Escape last space on a line,
        // see Rule#3 of http://www.ietf.org/rfc/rfc2045.txt page 19.
        if (input == Space && it != end && (*it == CarriageReturn || *it == LineFeed))
            escape = true;
        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;
            }
            _decodeLastChar = input;
        }
        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

  \preliminary
  \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 QLatin1String("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

  \preliminary
  \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 QLatin1String("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)
}


/*! \internal */
static QString encodeModifiedBase64(const QString &in)
{
    // Modified Base64 chars pattern
    const QString encodingSchema = QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,");

    QString result = QLatin1String("&");
    QList<ushort> buf;
    unsigned short tmp;
    int i;

    // chars to numeric
    for (i = 0; i < in.length(); i++) {
        buf.push_back(in[i].unicode());
    }

    i = 0;
    // encode every 6 bits separately in pattern by 3 symbols
    while (i < buf.length()) {

        result += encodingSchema[(buf[i] & 0xfc00) >> 10];
        result += encodingSchema[(buf[i] & 0x3f0) >> 4];

        tmp = 0;
        tmp |= ((buf[i] & 0xf) << 12);
        if (i + 1 < buf.length()) {
            i++;
            tmp |= ((buf[i] & 0xc000) >> 4);
            result += encodingSchema[tmp >> 10];
        } else {
            result += encodingSchema[tmp >> 10];
            break;
        }

        result += encodingSchema[(buf[i] & 0x3f00) >> 8];
        result += encodingSchema[(buf[i] & 0xfc) >> 2];

        tmp = 0;
        tmp |= ((buf[i] & 0x3) << 14);
        if (i + 1 < buf.length()) {
            i++;
            tmp |= ((buf[i] & 0xf000) >> 2);
            result += encodingSchema[tmp >> 10];
        } else {
            result += encodingSchema[tmp >> 10];
            break;
        }

        result += encodingSchema[(buf[i] & 0xfc0) >> 6];
        result += encodingSchema[buf[i] & 0x3f];
        i++;
    }

    result += QChar::fromLatin1('-');

    return result;
}

/*! \internal */
static QString decodeModifiedBase64(QString in)
{
    //remove  & -
    in.remove(0,1);
    in.remove(in.length()-1,1);

    if (in.isEmpty())
        return QLatin1String("&");

    QByteArray buf(in.length(),static_cast<char>(0));
    QByteArray out(in.length() * 3 / 4 + 2,static_cast<char>(0));

    //chars to numeric
    QByteArray latinChars = in.toLatin1();
    for (int x = 0; x < in.length(); x++) {
        int c = latinChars[x];
        if ( c >= 'A' && c <= 'Z')
            buf[x] = c - 'A';
        if ( c >= 'a' && c <= 'z')
            buf[x] = c - 'a' + 26;
        if ( c >= '0' && c <= '9')
            buf[x] = c - '0' + 52;
        if ( c == '+')
            buf[x] = 62;
        if ( c == ',')
            buf[x] = 63;
    }

    int i = 0; //in buffer index
    int j = i; //out buffer index

    unsigned char z;
    QString result;

    while (i+1 < buf.size()) {
        out[j] = buf[i] & (0x3F); //mask out top 2 bits
        out[j] = out[j] << 2;
        z = buf[i+1] >> 4;
        out[j] = (out[j] | z);      //first byte retrieved

        i++;
        j++;

        if (i+1 >= buf.size())
            break;

        out[j] = buf[i] & (0x0F);   //mask out top 4 bits
        out[j] = out[j] << 4;
        z = buf[i+1] >> 2;
        z &= 0x0F;
        out[j] = (out[j] | z);      //second byte retrieved

        i++;
        j++;

        if (i+1 >= buf.size())
            break;

        out[j] = buf[i] & 0x03;   //mask out top 6 bits
        out[j] = out[j] <<  6;
        z = buf[i+1];
        out[j] = out[j] | z;  //third byte retrieved

        i += 2; //next byte
        j++;
    }

    //go through the buffer and extract 16 bit unicode network byte order
    for (int z = 0; z < out.size(); z += 2) {
        unsigned short outcode = 0x0000;
        outcode = out[z];
        outcode <<= 8;
        outcode &= 0xFF00;

        unsigned short b = 0x0000;
        b = out[z+1];
        b &= 0x00FF;
        outcode = outcode | b;
        if (outcode)
            result += QChar(outcode);
    }

    return result;
}

/*!
    Encodes a \a text using modified UTF7 encoding according to RFC 3501, section 5.1.3

    \sa QMailCodec::decodeModifiedUtf7
*/
QString QMailCodec::encodeModifiedUtf7(const QString &text)
{
    QString in = text;
    int startIndex = 0;
    int endIndex = 0;

    while (startIndex < in.length()) {
        // insert '-' after '&'
        if (in[startIndex] == QChar::fromLatin1('&')) {
            startIndex++;
            in.insert(startIndex, QChar::fromLatin1('-'));
            continue;
        }

        if (in[startIndex].unicode() < 0x20 || in[startIndex].unicode() > 0x7e) {
            // get non-US-ASCII part
            endIndex = startIndex;
            while (endIndex < in.length() && (in[endIndex].unicode() < 0x20 || in[endIndex].unicode() > 0x7e))
                endIndex++;

            // encode non-US-ASCII part
            QString unicodeString = in.mid(startIndex,(endIndex - startIndex));
            QString mbase64 = encodeModifiedBase64(unicodeString);

            // insert the encoded string
            in.remove(startIndex,(endIndex-startIndex));
            in.insert(startIndex, mbase64);

            // set start index to the end of the encoded part
            startIndex += mbase64.length() - 1;
        }
        startIndex++;
    }
    return in;
}

/*!
    Decodes modified UTF7 \a text according to RFC 3501, section 5.1.3

    \sa QMailCodec::encodeModifiedUtf7
*/
QString QMailCodec::decodeModifiedUtf7(const QString &text)
{
    QString out;
    QRegularExpression reg(QLatin1String("&[^&-]*-"));
    QRegularExpressionMatchIterator it = reg.globalMatch(text);

    int startIndex = 0;
    int endIndex = 0;

    while (it.hasNext()) {
        QRegularExpressionMatch match = it.next();
        startIndex = match.capturedStart();
        out.append(text.mid(endIndex, startIndex - endIndex));

        //extract the base64 string from the input string
        out.append(decodeModifiedBase64(match.captured()));

        endIndex = match.capturedEnd();
    }
    out.append(text.mid(endIndex));

    return out;
}

