// uuencode_ex.cpp -- Shows how to convert uuencoded email to MIME

#include <iostream.h>
#include <fstream.h>
#include <mimepp/mimepp.h>


static int MatchesBeginPattern(const char *aPtr, int aPos, int aLen)
{
    // Pattern is: "begin ddd x"
    // where d is a digit and x is a filename character

    int matches = 0;
    const char *ptr = &aPtr[aPos];
    if (aLen - aPos >= 11
        && ptr[0] == 'b'
        && ptr[1] == 'e'
        && ptr[2] == 'g'
        && ptr[3] == 'i'
        && ptr[4] == 'n'
        && ptr[5] == ' '
        && 48 <= ptr[6] && ptr[6] <= 57
        && 48 <= ptr[7] && ptr[7] <= 57
        && 48 <= ptr[8] && ptr[8] <= 57
        && ptr[9] == ' '
        && 33 <= ptr[10] && ptr[10] <= 126) {

        matches = 1;
    }
    return matches;
}


static int FindEndPattern(const char* aPtr, int aPos, int aLen)
{
    int endPos = -1;
    int patternFound = 0;
    int pos = aPos;
    while (pos < aLen) {
        char ch = aPtr[pos++];
        if (ch == '\n') {
            if (aLen - pos >= 3
                && aPtr[pos  ] == 'e'
                && aPtr[pos+1] == 'n'
                && aPtr[pos+2] == 'd') {

                pos += 3;
                patternFound = 1;
                break;
            }
        }
    }
    if (patternFound) {
        // We matched the end pattern.  Now find either end of
        // current line of end of buffer
        while (pos < aLen) {
            char ch = aPtr[pos++];
            if (ch == '\n') {
                break;
            }
        }
        endPos = pos;
    }
    return endPos;
}


static void AddAttachment(DwBody& aBody, const DwString& aPartContent,
    const DwString& aFilename)
{
    // Create new body part

    DwBodyPart* part = DwBodyPart::NewBodyPart("", 0);

    // Set headers like this:
    //
    // Content-Type: application/octet-stream; name="filename"
    // Content-Disposition: attachment; filename="filename"

    part->Headers().ContentType().SetType(DwMime::kTypeApplication);
    part->Headers().ContentType().SetSubtype(DwMime::kSubtypeOctetStream);
    part->Headers().ContentType().SetName(aFilename);
    part->Headers().ContentDisposition()
		.SetDispositionType(DwMime::kDispTypeAttachment);
    part->Headers().ContentDisposition().SetFilename(aFilename);

    // Add the binary content to the body part after base64 encoding

    DwString encodedContent;
    DwEncodeBase64(aPartContent, encodedContent);
    part->Body().FromString(encodedContent);

    // Add the body part to the body

    aBody.AddBodyPart(part);
}


static void TextUuencodeToMime(DwEntity& aEntity)
{
    // Get the original body content

    DwString oldBodyContent = aEntity.Body().AsString();
    const char* oldBodyPtr = oldBodyContent.data();
    int oldBodyLen = oldBodyContent.length();

    // Create a new body and a string for the text part.

    DwString textContent;
    DwBody body;

    // Set first part in body part as the text part

    DwBodyPart* textBodyPart = DwBodyPart::NewBodyPart("", 0);
    textBodyPart->Headers().ContentType().SetType(DwMime::kTypeText);
    textBodyPart->Headers().ContentType().SetSubtype(DwMime::kSubtypePlain);
    body.AddBodyPart(textBodyPart);

    // Search for uuencoded attachments and extract them

    int pos = 0;
    int textStart = 0;
    int textEnd = 0;
    int attachStart = -1;
    int attachEnd = -1;
    int numAttachments = 0;
    while (pos < oldBodyLen) {
        // Find "\nb"
        char ch = oldBodyPtr[pos];
        ++pos;
        if (ch == '\n') {
            ch = oldBodyPtr[pos];
            if (ch == 'b') {
                int b = MatchesBeginPattern(oldBodyPtr, pos, oldBodyLen);
                if (b) {
                    textEnd = pos;
                    attachStart = pos;
                    int pos1 = FindEndPattern(oldBodyPtr, pos, oldBodyLen);
                    if (pos1 > 0) {
                        pos = pos1;
                        attachEnd = pos;
                        DwString textFragment = oldBodyContent.substr(
                            textStart, textEnd-textStart);
                        textContent += textFragment;
                        DwString attachAscii = oldBodyContent.substr(
                            attachStart, attachEnd-attachStart);
                        DwUuencode decoder;
                        decoder.SetAsciiChars(attachAscii);
                        decoder.Decode();
                        const DwString& attachBinary = decoder.BinaryChars();
                        DwString filename = decoder.FileName();
                        AddAttachment(body, attachBinary, filename);
                        ++numAttachments;
						textStart = pos;
						textEnd = pos;
                    }
                    else {
                        // Couldn't find "end" pattern, so let's quit here
                        break;
                    }
                }
            }
        }
    }
	textEnd = pos;
    

    // Only change the original if an attachment was found

    if (numAttachments > 0) {

        // Update the text content

        DwString textFragment = 
			oldBodyContent.substr(textStart, textEnd-textStart);
        textContent += textFragment;
        body.BodyPartAt(0).Body().FromString(textContent);

        // Change message type to multipart/mixed

        aEntity.Headers().ContentType().SetType(DwMime::kTypeMultipart);
        aEntity.Headers().ContentType().SetSubtype(DwMime::kSubtypeMixed);
        aEntity.Headers().ContentType().CreateBoundary();

        // Replace old body with new body

        aEntity.Body() = body;
    }
}


static void DataUuencodeToMime(DwEntity& aEntity)
{
    DwHeaders& headers = aEntity.Headers();
    DwBody& body = aEntity.Body();
    /*
     * If we have a content type other than text or message, then
     * look at the content-transfer-encoding.  If it's x-uuencode,
     * then convert to base64.
     */
    if (headers.HasContentTransferEncoding()) {
        const DwString& cte = headers.ContentTransferEncoding().AsString();
        if (DwStrcasecmp(cte, "x-uuencode") == 0) {
            /*
             * Decode the uuencoded content
             */
            DwString ascii = body.AsString();
            DwUuencode decoder;
            decoder.SetAsciiChars(ascii);
            decoder.Decode();
            const DwString& binary = decoder.BinaryChars();
            DwString filename = decoder.FileName();
            /*
             * Encode as base64 content
             */
            DwEncodeBase64(binary, ascii);
            body.FromString(ascii);
            headers.ContentTransferEncoding().FromEnum(DwMime::kCteBase64);
            headers.ContentType().SetName(filename);
        }
    }
}


static void UuencodeToMimeImpl(DwEntity& aEntity)
{
    DwHeaders& headers = aEntity.Headers();
    DwBody& body = aEntity.Body();
    int type = DwMime::kTypeText;
    int subtype = DwMime::kSubtypePlain;
    if (headers.HasContentType()) {
        type = headers.ContentType().Type();
        subtype = headers.ContentType().Subtype();
    }
    if (type == DwMime::kTypeText) {
        TextUuencodeToMime(aEntity);
    }
    else if (type == DwMime::kTypeMessage
        && subtype == DwMime::kSubtypeRfc822) {
        DwBody& body = aEntity.Body();
        DwMessage* msg = body.Message();
        if (msg != NULL) {
            UuencodeToMimeImpl(*msg);
        }
    }
    else if (type == DwMime::kTypeMultipart) {
        if (subtype == DwMime::kSubtypeMixed) {
            int numParts = body.NumBodyParts();
            for (int i=0; i < numParts; ++i) {
                DwBodyPart& part = body.BodyPartAt(i);
                UuencodeToMimeImpl(part);
            }
        }
    }
    else {
        DataUuencodeToMime(aEntity);
    }
}


void UuencodeToMime(DwEntity& aEntity)
{
    UuencodeToMimeImpl(aEntity);
    aEntity.Assemble();
}


int main(int argc, char** argv)
{
    // Initialize the library

#if ! defined(DW_WIN32) || defined(DW_NO_DLL)
    // Note: DwInitialize() is called in DllMain(), if you are using DLLs
    DwInitialize();
#endif

    // Read message from file

    DwString messageStr = "";
    DwString line;
    ifstream istrm(argv[1]);
    while (DwTrue) {
        getline(istrm, line);
        if (istrm.eof()) {
            break;
        }
        messageStr += line + DW_EOL;
    }
    istrm.close();
    
    // Create a DwMessage and parse it.  The DwMessage should be created on
    // the free store, since it will be added to the BasicMessage.

    DwMessage* msg = DwMessage::NewMessage(messageStr, 0);
    msg->Parse();

    UuencodeToMime(*msg);

    ofstream out("uu-out.txt");
	out << msg->AsString();
	out.close();

    return 0;
}
