//=============================================================================
// File:       uuencode.cpp
// Contents:   Definitions for DwUuencode
// Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
// WWW:        http://www.fwb.gulf.net/~dwsauder/mimepp.html
// $Revision: 151125 $
// $Date: 2002-04-22 11:45:44 +0200 (Mon, 22 Apr 2002) $
//
// 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 <stdio.h>
#include <string.h>
#include <ctype.h>
#include <mimelib/uuencode.h>

#if defined(DW_TESTING_UUENCODE)
#include <stdlib.h>
#include <time.h>
#include <iostream>
#include <fstream>
#endif


DwUuencode::DwUuencode()
{
    memset(mFileName, 0, sizeof(mFileName));
    mMode = 0644;
}


DwUuencode::~DwUuencode()
{
}


void DwUuencode::SetFileName(const char* aName)
{
    size_t n = sizeof(mFileName);
    strncpy(mFileName, aName, n);
	mFileName[n-1] = 0;
}


const char* DwUuencode::FileName() const
{
	return mFileName;
}


void DwUuencode::SetFileMode(DwUint16 aMode)
{
	mMode = aMode;
}


DwUint16 DwUuencode::FileMode() const
{
	return mMode;
}


void DwUuencode::SetBinaryChars(const DwString& aStr)
{
	mBinaryChars = aStr;
}


const DwString& DwUuencode::BinaryChars() const
{
	return mBinaryChars;
}


void DwUuencode::SetAsciiChars(const DwString& aStr)
{
	mAsciiChars = aStr;
}


const DwString& DwUuencode::AsciiChars() const
{
	return mAsciiChars;
}


#define ENC(c) ((char) ((c) ? ((c) & 0x3F) + ' ' : 96 ))


void DwUuencode::Encode()
{
	// Get input buffer

	size_t binLen = mBinaryChars.length();
	const char* binBuf = mBinaryChars.data();
	size_t binPos = 0;

	// Allocate buffer for binary chars

	size_t ascSize = (binLen+2)/3*4
		+ ((binLen+44)/45+1)*(strlen(DW_EOL)+1)
		+ strlen(mFileName)
		+ 13 + 2*strlen(DW_EOL)
		+ 100;
	DwString ascStr(ascSize, (char)0);
	char* ascBuf = (char*)ascStr.data();
	size_t ascPos = 0;

	// Write the "begin" line

	sprintf(ascBuf, "begin %o %s" DW_EOL, mMode, mFileName);
	ascPos = strlen(ascBuf);

	// Encode the binary chars

	while (1) {
		int numBinChars = binLen - binPos;
		numBinChars = (numBinChars <= 45) ? numBinChars : 45;
        ascBuf[ascPos++] = ENC(numBinChars);
		if (numBinChars == 0) {
			strcpy(&ascBuf[ascPos], DW_EOL);
			ascPos += strlen(DW_EOL);
			break;
		}
		int bin, asc;
		int binCharsDone = 0;
		while (binCharsDone <= numBinChars - 3) {

			bin = binBuf[binPos++];
			asc = (bin & 0xFC) >> 2;
			ascBuf[ascPos++] = ENC(asc);

			asc = (bin & 0x03) << 4;
			bin = binBuf[binPos++];
			asc |= (bin & 0xF0) >> 4;
			ascBuf[ascPos++] = ENC(asc);

			asc = (bin & 0x0F) << 2;
			bin = binBuf[binPos++];
			asc |= (bin & 0xC0) >> 6;
			ascBuf[ascPos++] = ENC(asc);

			asc = bin & 0x3F;
			ascBuf[ascPos++] = ENC(asc);

			binCharsDone += 3;
		}

		if (binCharsDone < numBinChars) {
			int binCharsLeft = numBinChars - binCharsDone;
			switch (binCharsLeft) {

			case 1:
				bin = binBuf[binPos++];
				asc = (bin & 0xFC) >> 2;
				ascBuf[ascPos++] = ENC(asc);

				asc = (bin & 0x03) << 4;
				ascBuf[ascPos++] = ENC(asc);

				ascBuf[ascPos++] = 96;
				ascBuf[ascPos++] = 96;
				break;

			case 2:
				bin = binBuf[binPos++];
				asc = (bin & 0xFC) >> 2;
				ascBuf[ascPos++] = ENC(asc);

				asc = (bin & 0x03) << 4;
				bin = binBuf[binPos++];
				asc |= (bin & 0xF0) >> 4;
				ascBuf[ascPos++] = ENC(asc);

				asc = (bin & 0x0F) << 2;
				ascBuf[ascPos++] = ENC(asc);

				ascBuf[ascPos++] = 96;
				break;

			default:
				break;
			}
		}

		strcpy(&ascBuf[ascPos], DW_EOL);
		ascPos += strlen(DW_EOL);
	}

	// Write the "end" line

	strcpy(&ascBuf[ascPos], "end" DW_EOL);
	ascPos += 3 + strlen(DW_EOL);
	ascBuf[ascPos] = 0;

	mAsciiChars.assign(ascStr, 0, ascPos);
}


#define DEC(c)  (((c) - ' ') & 0x3F)


int DwUuencode::Decode()
{
    int retVal = -1;

	// Get input buffer

    size_t ascLen = mAsciiChars.length();
    const char* ascBuf = mAsciiChars.data();
    size_t ascPos = 0;

    // Allocate destination buffer

    size_t binSize = (ascLen+3)/4*3;
    mBinaryChars.reserve(binSize);

	// Look for "begin " at beginning of buffer

	if (ascPos + 6 <= ascLen &&
	    strncmp(&ascBuf[ascPos], "begin ", 6) == 0) {

		ascPos += 6;
	}
	else {

		// Find "\nbegin " or "\rbegin "

		while (ascPos < ascLen) {
			int ch = ascBuf[ascPos++] & 0xff;
			switch (ch) {
			case '\n':
			case '\r':
				if (ascPos + 6 <= ascLen &&
					strncmp(&ascBuf[ascPos], "begin ", 6) == 0) {

					ascPos += 6;
					goto LOOP_EXIT_1;
				}
				break;
			default:
				break;
			}
		}
	}
LOOP_EXIT_1:

    // Get mode

    mMode = 0;
    while (ascPos < ascLen && isdigit(ascBuf[ascPos])) {
        mMode <<= 3;
        mMode += (DwUint16) (ascBuf[ascPos++] - '0');
    }

    // Get file name

    while (ascPos < ascLen &&
        (ascBuf[ascPos] == ' ' || ascBuf[ascPos] == '\t')) {

        ++ascPos;
    }
    size_t p1 = 0;
    while (ascPos < ascLen && p1 < sizeof(mFileName)-1 &&
        !isspace(ascBuf[ascPos])) {

        mFileName[p1++] = ascBuf[ascPos++];
    }
    mFileName[p1] = 0;

    // Advance to beginning of next line

    while (ascPos < ascLen) {
        int ch = ascBuf[ascPos++];
        switch (ch) {
        case '\n':
            goto LOOP_EXIT_2;
        case '\r':
            if (ascPos < ascLen && ascBuf[ascPos] == '\n') {
                ++ascPos;
            }
            goto LOOP_EXIT_2;
        default:
            break;
        }
    }
LOOP_EXIT_2:

    // Decode chars

    while (ascPos < ascLen) {
        int asc, bin;

        // Get number of binary chars in this line

        asc = ascBuf[ascPos++] & 0xff;
        size_t numBinChars = DEC(asc);
        if (numBinChars == 0) {
            break;
        }

        // Decode this line

        size_t binCharsEaten = 0;
        while (binCharsEaten <= numBinChars - 3 && ascPos <= ascLen - 4) {

            asc = ascBuf[ascPos++] & 0xff;
            bin = (DEC(asc) & 0x3F) << 2;
            asc = ascBuf[ascPos++] & 0xff;
            bin |= (DEC(asc) & 0x30) >> 4;
            mBinaryChars.append((size_t) 1, (char) bin);

            bin = (DEC(asc) & 0x0F) << 4;
            asc = ascBuf[ascPos++] & 0xff;
            bin |= (DEC(asc) & 0x3C) >> 2;
            mBinaryChars.append((size_t) 1, (char) bin);

            bin = (DEC(asc) & 0x03) << 6;
            asc = ascBuf[ascPos++] & 0xff;
            bin |= (DEC(asc) & 0x3F);
            mBinaryChars.append((size_t) 1, (char) bin);

            binCharsEaten += 3;
        }

    	// Special case if number of binary chars is not divisible by 3

    	if (binCharsEaten < numBinChars) {
    	    int binCharsLeft = numBinChars - binCharsEaten;
    	    switch (binCharsLeft) {
    	    case 2:
    	        if (ascPos >= ascLen)
    	            break;
    	        asc = ascBuf[ascPos++] & 0xff;
    	        bin = (DEC(asc) & 0x3F) << 2;
    	        if (ascPos >= ascLen)
    	            break;
    	        asc = ascBuf[ascPos++] & 0xff;
    	        bin |= (DEC(asc) & 0x30) >> 4;
                mBinaryChars.append((size_t) 1, (char) bin);

    	        bin = (DEC(asc) & 0x0F) << 4;
    	        if (ascPos >= ascLen)
    	            break;
    	        asc = ascBuf[ascPos++] & 0xff;
    	        bin |= (DEC(asc) & 0x3C) >> 2;
                mBinaryChars.append((size_t) 1, (char) bin);
    	        break;
    	    case 1:
    	        if (ascPos >= ascLen)
    	            break;
    	        asc = ascBuf[ascPos++] & 0xff;
    	        bin = (DEC(asc) & 0x3F) << 2;
    	        if (ascPos >= ascLen)
    	            break;
    	        asc = ascBuf[ascPos++] & 0xff;
    	        bin |= (DEC(asc) & 0x30) >> 4;
                mBinaryChars.append((size_t) 1, (char) bin);
    	        break;
    	    default:
    	        break;
    	    }
    	}

        // Advance to beginning of next line

        while (ascPos < ascLen) {
            int ch = ascBuf[ascPos++];
            switch (ch) {
            case '\n':
                goto LOOP_EXIT_3;
            case '\r':
                if (ascPos < ascLen &&
                    ascBuf[ascPos] == '\n') {

                    ++ascPos;
                }
                goto LOOP_EXIT_3;
            default:
                break;
            }
        }
LOOP_EXIT_3:
    	;
    }
    while (ascPos < ascLen) {
        int ch = ascBuf[ascPos++];
        switch (ch) {
        case '\n':
            goto LOOP_EXIT_4;
        case '\r':
            if (ascPos < ascLen &&
                ascBuf[ascPos] == '\n') {

                ++ascPos;
            }
            goto LOOP_EXIT_4;
        default:
            break;
        }
    }
LOOP_EXIT_4:
    if (ascPos + 3 <= ascLen &&
        strncmp(&ascBuf[ascPos], "end", 3) == 0) {

        retVal = 0;
    }
    return retVal;
}


#if defined(DW_TESTING_UUENCODE)

// Test harness for DwUudecode

int main(int argc, char** argv)
{
	srand(time(0));
	DwString binStr;
	binStr.reserve(5000);
	char ch;
	int i;
	for (i=0; i < 4000; ++i) {
		ch = rand()/(double)RAND_MAX*256;
		binStr += (char) ch;
	}
	for ( ; i < 4100; ++i) {
		binStr += (char) 0;
	}
	DwUuencode uu;
	uu.SetFileName("Testfile.dat");
	uu.SetMode(0600);
	uu.SetBinaryChars(binStr);
	uu.Encode();
	DwString asciiStr = uu.AsciiChars();
	// std::ofstream out("test.out", ios::out|ios::binary);
	std::ofstream out("test.out", ios::out);
	out << asciiStr;

	DwUuencode uu1;
	uu1.SetAsciiChars(uu.AsciiChars());
	uu1.Decode();

	size_t n = uu1.BinaryChars().length();
	const char* b1 = binStr.data();
	const char* b2 = uu1.BinaryChars().data();
	int bad = 0;
	for (i=0; i < n; ++i) {
		if (b1[i] != b2[i]) {
			cout << "Binary chars not equal at position " << i << "\n";
			bad = 1;
			break;
		}
	}
	if (! bad) {
		cout << "A-okay\n";
	}
	return 0;
}

#endif
