//=============================================================================
// File:       dw_cte.cpp
// Contents:   Function definitions for content transfer encodings
// Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
// WWW:        http://www.fwb.gulf.net/~dwsauder/mimepp.html
// $Revision: 1.8 $
// $Date: 2002/08/03 14:14:40 $
//
// Copyright (c) 1996, 1997 Douglas W. Sauder
// All rights reserved.
// 
// IN NO EVENT SHALL DOUGLAS W. SAUDER BE LIABLE TO ANY PARTY FOR DIRECT,
// INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF
// THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF DOUGLAS W. SAUDER
// HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// DOUGLAS W. SAUDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT
// NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
// BASIS, AND DOUGLAS W. SAUDER HAS NO OBLIGATION TO PROVIDE MAINTENANCE,
// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
//
//=============================================================================

#define DW_IMPLEMENTATION

#include <mimelib/config.h>
#include <mimelib/debug.h>
#include <string.h>
#include <mimelib/string.h>
#include <mimelib/utility.h>

#define MAXLINE  76

static size_t calc_crlf_buff_size(const char* srcBuf, size_t srcLen);
static int to_crlf(const char* srcBuf, size_t srcLen, char* destBuf,
    size_t destSize, size_t* destLen);
static int to_lf(const char* srcBuf, size_t srcLen, char* destBuf, 
    size_t destSize, size_t* destLen);
static int to_cr(const char* srcBuf, size_t srcLen, char* destBuf,
    size_t destSize, size_t* destLen);
static int encode_base64(const char* aIn, size_t aInLen, char* aOut,
    size_t aOutSize, size_t* aOutLen);
static int decode_base64(const char* aIn, size_t aInLen, char* aOut,
    size_t aOutSize, size_t* aOutLen);
static int encode_qp(const char* aIn, size_t aInLen, char* aOut,
    size_t aOutSize, size_t* aOutLen);
static int decode_qp(const char* aIn, size_t aInLen, char* aOut,
    size_t aOutSize, size_t* aOutLen);
static size_t calc_qp_buff_size(const char* aIn, size_t aInLen);


int DwToCrLfEol(const DwString& aSrcStr, DwString& aDestStr)
{
    // Estimate required destination buffer size
    size_t srcLen = aSrcStr.length();
    const char* srcBuf = aSrcStr.data();
    size_t destSize = calc_crlf_buff_size(srcBuf, srcLen);

    // Allocate destination buffer
    DwString destStr(destSize, (char)0);
    char* destBuf = (char*) destStr.data();

    // Encode source to destination
    size_t destLen;
    to_crlf(srcBuf, srcLen, destBuf, destSize, &destLen);
    aDestStr.assign(destStr, 0, destLen);
    return 0;
}


int DwToLfEol(const DwString& aSrcStr, DwString& aDestStr)
{
    size_t srcLen = aSrcStr.length();
    const char* srcBuf = aSrcStr.data();
    size_t destSize = srcLen;
    
    // Allocate destination buffer
    DwString destStr(destSize, (char)0);
    char* destBuf = (char*) destStr.data();

    // Encode source to destination
    size_t destLen;
    to_lf(srcBuf, srcLen, destBuf, destSize, &destLen);
    aDestStr.assign(destStr, 0, destLen);
    return 0;
}


int DwToCrEol(const DwString& aSrcStr, DwString& aDestStr)
{
    size_t srcLen = aSrcStr.length();
    const char* srcBuf = aSrcStr.data();
    size_t destSize = srcLen;
    
    // Allocate destination buffer
    DwString destStr(destSize, (char)0);
    char* destBuf = (char*) destStr.data();

    // Encode source to destination
    size_t destLen;
    to_cr(srcBuf, srcLen, destBuf, destSize, &destLen);
    aDestStr.assign(destStr, 0, destLen);
    return 0;
}


int DwToLocalEol(const DwString& aSrcStr, DwString& aDestStr)
{
#if defined(DW_EOL_CRLF)
    return DwToCrLfEol(aSrcStr, aDestStr);
#elif defined(DW_EOL_LF)
    return DwToLfEol(aSrcStr, aDestStr);
#else
#   error "Must define DW_EOL_CRLF, DW_EOL_LF"
#endif
}


int DwEncodeBase64(const DwString& aSrcStr, DwString& aDestStr)
{
    // Estimate required destination buffer size
    size_t srcLen = aSrcStr.length();
    const char* srcBuf = aSrcStr.data();
    size_t destSize = (srcLen+2)/3*4;
    destSize += strlen(DW_EOL)*destSize/72 + 2;
    destSize += 64;  // a little extra room

    // Allocate destination buffer
    DwString destStr(destSize, (char)0);
    char* destBuf = (char*) destStr.data();

    // Encode source to destination
    size_t destLen;
    int result =
    encode_base64(srcBuf, srcLen, destBuf, destSize, &destLen);
    aDestStr.assign(destStr, 0, destLen);
    return result;
}


int DwDecodeBase64(const DwString& aSrcStr, DwString& aDestStr)
{
    // Set destination buffer size same as source buffer size
    size_t srcLen = aSrcStr.length();
    const char* srcBuf = aSrcStr.data();
    size_t destSize = srcLen;

    // Allocate destination buffer
    DwString destStr(destSize, (char)0);
    char* destBuf = (char*) destStr.data();

    // Encode source to destination
    size_t destLen;
    int result =
    decode_base64(srcBuf, srcLen, destBuf, destSize, &destLen);
    aDestStr.assign(destStr, 0, destLen);
    return result;
}


int DwEncodeQuotedPrintable(const DwString& aSrcStr, DwString& aDestStr)
{
    // Estimate required destination buffer size
    size_t srcLen = aSrcStr.length();
    const char* srcBuf = aSrcStr.data();
    size_t destSize = calc_qp_buff_size(srcBuf, srcLen);
    destSize += 64;  // a little extra room

    // Allocate destination buffer
    DwString destStr(destSize, (char)0);
    char* destBuf = (char*) destStr.data();
    
    // Encode source to destination
    size_t destLen;
    int result =
    encode_qp(srcBuf, srcLen, destBuf, destSize, &destLen);
    aDestStr.assign(destStr, 0, destLen);
    return result;
}


int DwDecodeQuotedPrintable(const DwString& aSrcStr, DwString& aDestStr)
{
    // Set destination buffer size same as source buffer size
    size_t srcLen = aSrcStr.length();
    const char* srcBuf = aSrcStr.data();
    size_t destSize = srcLen;
    
    // Allocate destination buffer
    DwString destStr(destSize, (char)0);
    char* destBuf = (char*) destStr.data();
    
    // Encode source to destination
    size_t destLen;
    int result = 
    decode_qp(srcBuf, srcLen, destBuf, destSize, &destLen);
    aDestStr.assign(destStr, 0, destLen);
    return result;
}


//============================================================================
// Everything below this line is private to this file (static)
//============================================================================


static size_t calc_crlf_buff_size(const char* srcBuf, size_t srcLen)
{
    size_t i, extra;

    if (!srcBuf) return 0;
    extra = 0;
    for (i=0; i < srcLen; ) {
        switch (srcBuf[i]) {
        /* Bare LF (UNIX or C text) */
        case '\n':
            ++extra;
            ++i;
            break;
        case '\r':
            /* CR LF (DOS, Windows, or MIME text) */
            if (i+1 < srcLen && srcBuf[i+1] == '\n') {
                i += 2;
            }
            /* Bare CR (Macintosh text) */
            else {
                ++extra;
                ++i;
            }
            break;
        default:
            ++i;
        }
    }
    return srcLen + extra;
}


static int to_crlf(const char* srcBuf, size_t srcLen, char* destBuf,
    size_t destSize, size_t* destLen)
{
    size_t iSrc, iDest;

    if (!srcBuf || !destBuf || !destLen) return -1;
    iSrc = iDest = 0;
    while (iSrc < srcLen && iDest < destSize) {
        switch (srcBuf[iSrc]) {
        /* Bare LF (UNIX or C text) */
        case '\n':
            destBuf[iDest++] = '\r';
            if (iDest < destSize) {
                destBuf[iDest++] = srcBuf[iSrc++];
            }
            break;
        case '\r':
            /* CR LF (DOS, Windows, or MIME text) */
            if (iSrc+1 < srcLen && srcBuf[iSrc+1] == '\n') {
                destBuf[iDest++] = srcBuf[iSrc++];
                if (iDest < destSize) {
                    destBuf[iDest++] = srcBuf[iSrc++];
                }
            }
            /* Bare CR (Macintosh text) */
            else {
                destBuf[iDest++] = srcBuf[iSrc++];
                if (iDest < destSize) {
                    destBuf[iDest++] = '\n';
                }
            }
            break;
        default:
            destBuf[iDest++] = srcBuf[iSrc++];
        }
    }
    *destLen = iDest;
    if (iDest < destSize) {
        destBuf[iDest] = 0;
    }
    return 0;
}


static int to_lf(const char* srcBuf, size_t srcLen, char* destBuf,
    size_t destSize, size_t* destLen)
{
    size_t iSrc, iDest;

    if (!srcBuf || !destBuf || !destLen) return -1;
    iSrc = iDest = 0;
    while (iSrc < srcLen && iDest < destSize) {
        switch (srcBuf[iSrc]) {
        /* Bare LF (UNIX or C text) */
        case '\n':
            destBuf[iDest++] = srcBuf[iSrc++];
            break;
        case '\r':
            /* CR LF (DOS, Windows, or MIME text) */
            if (iSrc+1 < srcLen && srcBuf[iSrc+1] == '\n') {
                ++iSrc;
                destBuf[iDest++] = srcBuf[iSrc++];
            }
            /* Bare CR (Macintosh text) */
            else {
                destBuf[iDest++] = '\n';
                ++iSrc;
            }
            break;
        default:
            destBuf[iDest++] = srcBuf[iSrc++];
        }
    }
    *destLen = iDest;
    if (iDest < destSize) {
        destBuf[iDest] = 0;
    }
    return 0;
}


static int to_cr(const char* srcBuf, size_t srcLen, char* destBuf,
    size_t destSize, size_t* destLen)
{
    size_t iSrc, iDest;

    if (!srcBuf || !destBuf || !destLen) return -1;
    iSrc = iDest = 0;
    while (iSrc < srcLen && iDest < destSize) {
        switch (srcBuf[iSrc]) {
        /* Bare LF (UNIX or C text) */
        case '\n':
            destBuf[iDest++] = '\r';
            ++iSrc;
            break;
        case '\r':
            /* CR LF (DOS, Windows, or MIME text) */
            if (iSrc+1 < srcLen && srcBuf[iSrc+1] == '\n') {
                destBuf[iDest++] = srcBuf[iSrc++];
                ++iSrc;
            }
            /* Bare CR (Macintosh text) */
            else {
                destBuf[iDest++] = srcBuf[iSrc++];
            }
            break;
        default:
            destBuf[iDest++] = srcBuf[iSrc++];
        }
    }
    *destLen = iDest;
    if (iDest < destSize) {
        destBuf[iDest] = 0;
    }
    return 0;
}


static char base64tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz0123456789+/";

static char base64idx[128] = {
    '\377','\377','\377','\377','\377','\377','\377','\377',
    '\377','\377','\377','\377','\377','\377','\377','\377',
    '\377','\377','\377','\377','\377','\377','\377','\377',
    '\377','\377','\377','\377','\377','\377','\377','\377',
    '\377','\377','\377','\377','\377','\377','\377','\377',
    '\377','\377','\377',    62,'\377','\377','\377',    63,
        52,    53,    54,    55,    56,    57,    58,    59,
        60,    61,'\377','\377','\377','\377','\377','\377',
    '\377',     0,     1,     2,     3,     4,     5,     6,
         7,     8,     9,    10,    11,    12,    13,    14,
        15,    16,    17,    18,    19,    20,    21,    22,
        23,    24,    25,'\377','\377','\377','\377','\377',
    '\377',    26,    27,    28,    29,    30,    31,    32,
        33,    34,    35,    36,    37,    38,    39,    40,
        41,    42,    43,    44,    45,    46,    47,    48,
        49,    50,    51,'\377','\377','\377','\377','\377'
};

static char hextab[] = "0123456789ABCDEF";

#ifdef __cplusplus
inline int isbase64(int a) {
    return ('A' <= a && a <= 'Z')
        || ('a' <= a && a <= 'z')
        || ('0' <= a && a <= '9')
        || a == '+' || a == '/';
}
#else
#define isbase64(a) (  ('A' <= (a) && (a) <= 'Z') \
                    || ('a' <= (a) && (a) <= 'z') \
                    || ('0' <= (a) && (a) <= '9') \
                    ||  (a) == '+' || (a) == '/'  )
#endif


static int encode_base64(const char* aIn, size_t aInLen, char* aOut,
    size_t aOutSize, size_t* aOutLen)
{
    if (!aIn || !aOut || !aOutLen)
        return -1;
    size_t inLen = aInLen;
    char* out = aOut;
    size_t outSize = (inLen+2)/3*4;     /* 3:4 conversion ratio */
    outSize += strlen(DW_EOL)*outSize/MAXLINE + 2;  /* Space for newlines and NUL */
    if (aOutSize < outSize)
        return -1;
    size_t inPos  = 0;
    size_t outPos = 0;
    int c1, c2, c3;
    int lineLen = 0;
    /* Get three characters at a time and encode them. */
    for (size_t i=0; i < inLen/3; ++i) {
        c1 = aIn[inPos++] & 0xFF;
        c2 = aIn[inPos++] & 0xFF;
        c3 = aIn[inPos++] & 0xFF;
        out[outPos++] = base64tab[(c1 & 0xFC) >> 2];
        out[outPos++] = base64tab[((c1 & 0x03) << 4) | ((c2 & 0xF0) >> 4)];
        out[outPos++] = base64tab[((c2 & 0x0F) << 2) | ((c3 & 0xC0) >> 6)];
        out[outPos++] = base64tab[c3 & 0x3F];
		lineLen += 4;
        if (lineLen >= MAXLINE-3) {
			const char* cp = DW_EOL;
            out[outPos++] = *cp++;
			if (*cp) {
				out[outPos++] = *cp;
			}
			lineLen = 0;
        }
    }
    /* Encode the remaining one or two characters. */
	const char* cp;
    switch (inLen % 3) {
    case 0:
		cp = DW_EOL;
        out[outPos++] = *cp++;
		if (*cp) {
			out[outPos++] = *cp;
		}
        break;
    case 1:
        c1 = aIn[inPos] & 0xFF;
        out[outPos++] = base64tab[(c1 & 0xFC) >> 2];
        out[outPos++] = base64tab[((c1 & 0x03) << 4)];
        out[outPos++] = '=';
        out[outPos++] = '=';
		cp = DW_EOL;
        out[outPos++] = *cp++;
		if (*cp) {
			out[outPos++] = *cp;
		}
        break;
    case 2:
        c1 = aIn[inPos++] & 0xFF;
        c2 = aIn[inPos] & 0xFF;
        out[outPos++] = base64tab[(c1 & 0xFC) >> 2];
        out[outPos++] = base64tab[((c1 & 0x03) << 4) | ((c2 & 0xF0) >> 4)];
        out[outPos++] = base64tab[((c2 & 0x0F) << 2)];
        out[outPos++] = '=';
		cp = DW_EOL;
        out[outPos++] = *cp++;
		if (*cp) {
			out[outPos++] = *cp;
		}
        break;
    }
    out[outPos] = 0;
    *aOutLen = outPos;
    return 0;
}


static int decode_base64(const char* aIn, size_t aInLen, char* aOut,
    size_t aOutSize, size_t* aOutLen)
{
    if (!aIn || !aOut || !aOutLen)
        return -1;
    size_t inLen = aInLen;
    char* out = aOut;
    size_t outSize = ( ( inLen + 3 ) / 4 ) * 3;
    if (aOutSize < outSize)
        return -1;
    /* Get four input chars at a time and decode them. Ignore white space
     * chars (CR, LF, SP, HT). If '=' is encountered, terminate input. If
     * a char other than white space, base64 char, or '=' is encountered,
     * flag an input error, but otherwise ignore the char.
     */
    int isErr = 0;
    int isEndSeen = 0;
    int b1, b2, b3;
    int a1, a2, a3, a4;
    size_t inPos = 0;
    size_t outPos = 0;
    while (inPos < inLen) {
        a1 = a2 = a3 = a4 = 0;
        while (inPos < inLen) {
            a1 = aIn[inPos++] & 0xFF;
            if (isbase64(a1)) {
                break;
            }
            else if (a1 == '=') {
                isEndSeen = 1;
                break;
            }
            else if (a1 != '\r' && a1 != '\n' && a1 != ' ' && a1 != '\t') {
                isErr = 1;
            }
        }
        while (inPos < inLen) {
            a2 = aIn[inPos++] & 0xFF;
            if (isbase64(a2)) {
                break;
            }
            else if (a2 == '=') {
                isEndSeen = 1;
                break;
            }
            else if (a2 != '\r' && a2 != '\n' && a2 != ' ' && a2 != '\t') {
                isErr = 1;
            }
        }
        while (inPos < inLen) {
            a3 = aIn[inPos++] & 0xFF;
            if (isbase64(a3)) {
                break;
            }
            else if (a3 == '=') {
                isEndSeen = 1;
                break;
            }
            else if (a3 != '\r' && a3 != '\n' && a3 != ' ' && a3 != '\t') {
                isErr = 1;
            }
        }
        while (inPos < inLen) {
            a4 = aIn[inPos++] & 0xFF;
            if (isbase64(a4)) {
                break;
            }
            else if (a4 == '=') {
                isEndSeen = 1;
                break;
            }
            else if (a4 != '\r' && a4 != '\n' && a4 != ' ' && a4 != '\t') {
                isErr = 1;
            }
        }
        if (isbase64(a1) && isbase64(a2) && isbase64(a3) && isbase64(a4)) {
            a1 = base64idx[a1] & 0xFF;
            a2 = base64idx[a2] & 0xFF;
            a3 = base64idx[a3] & 0xFF;
            a4 = base64idx[a4] & 0xFF;
            b1 = ((a1 << 2) & 0xFC) | ((a2 >> 4) & 0x03);
            b2 = ((a2 << 4) & 0xF0) | ((a3 >> 2) & 0x0F);
            b3 = ((a3 << 6) & 0xC0) | ( a4       & 0x3F);
            out[outPos++] = char(b1);
            out[outPos++] = char(b2);
            out[outPos++] = char(b3);
        }
        else if (isbase64(a1) && isbase64(a2) && isbase64(a3) && a4 == '=') {
            a1 = base64idx[a1] & 0xFF;
            a2 = base64idx[a2] & 0xFF;
            a3 = base64idx[a3] & 0xFF;
            b1 = ((a1 << 2) & 0xFC) | ((a2 >> 4) & 0x03);
            b2 = ((a2 << 4) & 0xF0) | ((a3 >> 2) & 0x0F);
            out[outPos++] = char(b1);
            out[outPos++] = char(b2);
            break;
        }
        else if (isbase64(a1) && isbase64(a2) && a3 == '=' && a4 == '=') {
            a1 = base64idx[a1] & 0xFF;
            a2 = base64idx[a2] & 0xFF;
            b1 = ((a1 << 2) & 0xFC) | ((a2 >> 4) & 0x03);
            out[outPos++] = char(b1);
            break;
        }
        else {
            break;
        }
        if (isEndSeen) {
            break;
        }
    } /* end while loop */
    *aOutLen = outPos;
    return (isErr) ? -1 : 0;
}


/***** Warning: calc_qp_buff_size must stay in sync with encode_qp ******/

static int encode_qp(const char* aIn, size_t aInLen, char* aOut,
    size_t /*aOutSize */, size_t* aOutLen)
{
    size_t inPos, outPos, lineLen;
    int ch;

    if (!aIn || !aOut || !aOutLen) {
        return -1;
    }
    inPos  = 0;
    outPos = 0;
    lineLen = 0;
    while (inPos < aInLen) {
        ch = aIn[inPos++] & 0xFF;
        /* '.' at beginning of line (confuses some SMTPs) */
        if (lineLen == 0 && ch == '.') {
            aOut[outPos++] = '=';
            aOut[outPos++] = hextab[(ch >> 4) & 0x0F];
            aOut[outPos++] = hextab[ch & 0x0F];
            lineLen += 3;
        }
        /* "From " at beginning of line (gets mangled in mbox folders) */
        else if (lineLen == 0 && inPos+3 < aInLen && ch == 'F'
                 && aIn[inPos  ] == 'r' && aIn[inPos+1] == 'o'
                 && aIn[inPos+2] == 'm' && aIn[inPos+3] == ' ') {
            aOut[outPos++] = '=';
            aOut[outPos++] = hextab[(ch >> 4) & 0x0F];
            aOut[outPos++] = hextab[ch & 0x0F];
            lineLen += 3;
        }
        /* Normal printable char */
        else if ((62 <= ch && ch <= 126) || (33 <= ch && ch <= 60)) {
            aOut[outPos++] = (char) ch;
            ++lineLen;
        }
        /* Space */
        else if (ch == ' ') {
            /* Space at end of line or end of input must be encoded */
#if defined(DW_EOL_LF)
            if (inPos >= aInLen           /* End of input? */
                || aIn[inPos] == '\n') {  /* End of line? */

                aOut[outPos++] = '=';
                aOut[outPos++] = '2';
                aOut[outPos++] = '0';
                lineLen += 3;
            }
#elif defined(DW_EOL_CRLF)
			if (inPos >= aInLen           /* End of input? */
				|| (inPos < aInLen-1      /* End of line? */
				    && aIn[inPos  ] == '\r' 
					&& aIn[inPos+1] == '\n') ) {

                aOut[outPos++] = '=';
                aOut[outPos++] = '2';
                aOut[outPos++] = '0';
                lineLen += 3;
			}
#else
# error Must define DW_EOL_LF or DW_EOL_CRLF
#endif
            else {
                aOut[outPos++] = ' ';
                ++lineLen;
            }
        }
        /* Hard line break */
#if defined(DW_EOL_LF)
        else if (ch == '\n') {
            aOut[outPos++] = '\n';
            lineLen = 0;
        }
#elif defined(DW_EOL_CRLF)
        else if (inPos < aInLen && ch == '\r' && aIn[inPos] == '\n') {
            ++inPos;
            aOut[outPos++] = '\r';
            aOut[outPos++] = '\n';
            lineLen = 0;
        }
#endif
        /* Non-printable char */
        else if (ch & 0x80        /* 8-bit char */
                 || !(ch & 0xE0)  /* control char */
                 || ch == 0x7F    /* DEL */
                 || ch == '=') {  /* special case */
            aOut[outPos++] = '=';
            aOut[outPos++] = hextab[(ch >> 4) & 0x0F];
            aOut[outPos++] = hextab[ch & 0x0F];
            lineLen += 3;
        }
        /* Soft line break */
#if defined(DW_EOL_LF)
        if (lineLen >= MAXLINE-3 && inPos < aInLen && aIn[inPos] != '\n') {
            aOut[outPos++] = '=';
            aOut[outPos++] = '\n';
            lineLen = 0;
        }
#elif defined(DW_EOL_CRLF)
        if (lineLen >= MAXLINE-3 && !(inPos < aInLen-1 && 
			aIn[inPos] == '\r' && aIn[inPos+1] == '\n')) {

            aOut[outPos++] = '=';
            aOut[outPos++] = '\r';
            aOut[outPos++] = '\n';
            lineLen = 0;
        }
#endif
    }
    aOut[outPos] = 0;
    *aOutLen = outPos;
    return 0;
}


static int decode_qp(const char* aIn, size_t aInLen, char* aOut,
    size_t /* aOutSize */, size_t* aOutLen)
{
    size_t i, inPos, outPos, lineLen, nextLineStart, numChars, charsEnd;
    int isEolFound, softLineBrk, isError;
    int ch, c1, c2;

    if (!aIn || !aOut || !aOutLen)
        return -1;
    isError = 0;
    inPos = 0;
    outPos = 0;
    for (i=0; i < aInLen; ++i) {
        if (aIn[i] == 0) {
            aInLen = i;
            break;
        }
    }
    if (aInLen == 0) {
        aOut[0] = 0;
        *aOutLen = 0;
        return 0;
    }
    while (inPos < aInLen) {
        /* Get line */
        lineLen = 0;
        isEolFound = 0;
        while (!isEolFound && lineLen < aInLen - inPos) {
            ch = aIn[inPos+lineLen];
            ++lineLen;
            if (ch == '\n') {
                isEolFound = 1;
            }
        }
        nextLineStart = inPos + lineLen;
        numChars = lineLen;
        /* Remove white space from end of line */
        while (numChars > 0) {
            ch = aIn[inPos+numChars-1] & 0x7F;
            if (ch != '\n' && ch != '\r' && ch != ' ' && ch != '\t') {
                break;
            }
            --numChars;
        }
        charsEnd = inPos + numChars;
        /* Decode line */
        softLineBrk = 0;
        while (inPos < charsEnd) {
            ch = aIn[inPos++] & 0x7F;
            if (ch != '=') {
                /* Normal printable char */
                aOut[outPos++] = (char) ch;
            }
            else /* if (ch == '=') */ {
                /* Soft line break */
                if (inPos >= charsEnd) {
                    softLineBrk = 1;
                    break;
                }
                /* Non-printable char */
                else if (inPos < charsEnd-1) {
                    c1 = aIn[inPos++] & 0x7F;
                    if ('0' <= c1 && c1 <= '9')
                        c1 -= '0';
                    else if ('A' <= c1 && c1 <= 'F')
                        c1 = c1 - 'A' + 10;
                    else if ('a' <= c1 && c1 <= 'f')
                        c1 = c1 - 'a' + 10;
                    else
                        isError = 1;
                    c2 = aIn[inPos++] & 0x7F;
                    if ('0' <= c2 && c2 <= '9')
                        c2 -= '0';
                    else if ('A' <= c2 && c2 <= 'F')
                        c2 = c2 - 'A' + 10;
                    else if ('a' <= c2 && c2 <= 'f')
                        c2 = c2 - 'a' + 10;
                    else
                        isError = 1;
                    aOut[outPos++] = (char) ((c1 << 4) + c2);
                }
                else /* if (inPos == charsEnd-1) */ {
                    isError = 1;
                }
            }
        }
        if (isEolFound && !softLineBrk) {
            const char* cp = DW_EOL;
            aOut[outPos++] = *cp++;
            if (*cp) {
                aOut[outPos++] = *cp;
            }
        }
        inPos = nextLineStart;
    }
    aOut[outPos] = 0;
    *aOutLen = outPos;
    return (isError) ? -1 : 0;
}


/***** Warning: calc_qp_buff_size must stay in sync with encode_qp ******/

static size_t calc_qp_buff_size(const char* aIn, size_t aInLen)
{
    size_t inPos, outLen, lineLen;
    int ch;

    if (!aIn || aInLen == 0) {
        return 0;
    }
    inPos  = 0;
    outLen = 0;
    lineLen = 0;
    while (inPos < aInLen) {
        ch = aIn[inPos++] & 0xFF;
        /* '.' at beginning of line (confuses some SMTPs) */
        if (lineLen == 0 && ch == '.') {
            outLen += 3;
            lineLen += 3;
        }
        /* "From " at beginning of line (gets mangled in mbox folders) */
        else if (lineLen == 0 && inPos+3 < aInLen && ch == 'F'
                 && aIn[inPos  ] == 'r' && aIn[inPos+1] == 'o'
                 && aIn[inPos+2] == 'm' && aIn[inPos+3] == ' ') {
            outLen += 3;
            lineLen += 3;
        }
        /* Normal printable char */
        else if ((62 <= ch && ch <= 126) || (33 <= ch && ch <= 60)) {
            ++outLen;
            ++lineLen;
        }
        /* Space */
        else if (ch == ' ') {
            /* Space at end of line or end of input must be encoded */
#if defined(DW_EOL_LF)
            if (inPos >= aInLen           /* End of input? */
                || aIn[inPos] == '\n') {  /* End of line? */

                outLen += 3;
                lineLen += 3;
            }
#elif defined(DW_EOL_CRLF)
			if (inPos >= aInLen           /* End of input? */
				|| (inPos < aInLen-1      /* End of line? */
				    && aIn[inPos  ] == '\r' 
					&& aIn[inPos+1] == '\n') ) {

                outLen += 3;
                lineLen += 3;
			}
#else
# error Must define DW_EOL_LF or DW_EOL_CRLF
#endif
            else {
                ++outLen;
                ++lineLen;
            }
        }
        /* Hard line break */
#if defined(DW_EOL_LF)
        else if (ch == '\n') {
            ++outLen;
            lineLen = 0;
        }
#elif defined(DW_EOL_CRLF)
        else if (inPos < aInLen && ch == '\r' && aIn[inPos] == '\n') {
            ++inPos;
            outLen += 2;
            lineLen = 0;
        }
#endif
        /* Non-printable char */
        else if (ch & 0x80        /* 8-bit char */
                 || !(ch & 0xE0)  /* control char */
                 || ch == 0x7F    /* DEL */
                 || ch == '=') {  /* special case */
            outLen += 3;
            lineLen += 3;
        }
        /* Soft line break */
#if defined(DW_EOL_LF)
        if (lineLen >= MAXLINE-3 && inPos < aInLen && aIn[inPos] != '\n') {
            outLen += 2;
            lineLen = 0;
        }
#elif defined(DW_EOL_CRLF)
        if (lineLen >= MAXLINE-3 && !(inPos < aInLen-1 && 
			aIn[inPos] == '\r' && aIn[inPos+1] == '\n')) {

            outLen += 3;
            lineLen = 0;
        }
#endif
    }
    return outLen;
}

