root/src/kits/mail/MailAttachment.cpp
/*
 * Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
 */


/*! Classes which handle mail attachments */


#include <MailAttachment.h>

#include <stdlib.h>
#include <stdio.h>

#include <ByteOrder.h>
#include <DataIO.h>
#include <Entry.h>
#include <File.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <String.h>

#include <AutoDeleter.h>

#include <mail_encoding.h>
#include <NodeMessage.h>


/*! No attributes or awareness of the file system at large
*/
BSimpleMailAttachment::BSimpleMailAttachment()
        :
        fStatus(B_NO_INIT),
        _data(NULL),
        _raw_data(NULL),
        _we_own_data(false)
{
        Initialize(base64);
}


BSimpleMailAttachment::BSimpleMailAttachment(BPositionIO *data,
        mail_encoding encoding)
        :
        _data(data),
        _raw_data(NULL),
        _we_own_data(false)
{
        fStatus = data == NULL ? B_BAD_VALUE : B_OK;

        Initialize(encoding);
}


BSimpleMailAttachment::BSimpleMailAttachment(const void *data, size_t length,
        mail_encoding encoding)
        :
        _data(new BMemoryIO(data,length)),
        _raw_data(NULL),
        _we_own_data(true)
{
        fStatus = data == NULL ? B_BAD_VALUE : B_OK;

        Initialize(encoding);
}


BSimpleMailAttachment::BSimpleMailAttachment(BFile *file, bool deleteWhenDone)
        :
        _data(NULL),
        _raw_data(NULL),
        _we_own_data(false)
{
        Initialize(base64);
        SetTo(file, deleteWhenDone);
}


BSimpleMailAttachment::BSimpleMailAttachment(entry_ref *ref)
        :
        _data(NULL),
        _raw_data(NULL),
        _we_own_data(false)
{
        Initialize(base64);
        SetTo(ref);
}


BSimpleMailAttachment::~BSimpleMailAttachment()
{
        if (_we_own_data)
                delete _data;
}


void
BSimpleMailAttachment::Initialize(mail_encoding encoding)
{
        SetEncoding(encoding);
        SetHeaderField("Content-Disposition","BMailAttachment");
}


status_t
BSimpleMailAttachment::SetTo(BFile *file, bool deleteFileWhenDone)
{
        char type[B_MIME_TYPE_LENGTH] = "application/octet-stream";

        BNodeInfo nodeInfo(file);
        if (nodeInfo.InitCheck() == B_OK)
                nodeInfo.GetType(type);

        SetHeaderField("Content-Type", type);
        // TODO: No way to get file name (see SetTo(entry_ref *))
        //SetFileName(ref->name);

        if (deleteFileWhenDone)
                SetDecodedDataAndDeleteWhenDone(file);
        else
                SetDecodedData(file);

        return fStatus = B_OK;
}


status_t
BSimpleMailAttachment::SetTo(entry_ref *ref)
{
        BFile *file = new BFile(ref, B_READ_ONLY);
        if ((fStatus = file->InitCheck()) < B_OK) {
                delete file;
                return fStatus;
        }

        if (SetTo(file, true) != B_OK)
                return fStatus;

        SetFileName(ref->name);
        return fStatus = B_OK;
}


status_t
BSimpleMailAttachment::InitCheck()
{
        return fStatus;
}


status_t
BSimpleMailAttachment::FileName(char *text)
{
        BMessage contentType;
        HeaderField("Content-Type", &contentType);

        const char *fileName = contentType.FindString("name");
        if (!fileName)
                fileName = contentType.FindString("filename");
        if (!fileName) {
                contentType.MakeEmpty();
                HeaderField("Content-Disposition", &contentType);
                fileName = contentType.FindString("name");
        }
        if (!fileName)
                fileName = contentType.FindString("filename");
        if (!fileName) {
                contentType.MakeEmpty();
                HeaderField("Content-Location", &contentType);
                fileName = contentType.FindString("unlabeled");
        }
        if (!fileName)
                return B_NAME_NOT_FOUND;

        strncpy(text, fileName, B_FILE_NAME_LENGTH);
        return B_OK;
}


void
BSimpleMailAttachment::SetFileName(const char *name)
{
        BMessage contentType;
        HeaderField("Content-Type", &contentType);

        if (contentType.ReplaceString("name", name) != B_OK)
                contentType.AddString("name", name);

        // Request that the file name header be encoded in UTF-8 if it has weird
        // characters.  If it is just a plain name, the header will appear normal.
        if (contentType.ReplaceInt32(kHeaderCharsetString, B_MAIL_UTF8_CONVERSION)
                        != B_OK)
                contentType.AddInt32(kHeaderCharsetString, B_MAIL_UTF8_CONVERSION);

        SetHeaderField ("Content-Type", &contentType);
}


status_t
BSimpleMailAttachment::GetDecodedData(BPositionIO *data)
{
        ParseNow();

        if (!_data)
                return B_IO_ERROR;
        if (data == NULL)
                return B_BAD_VALUE;

        char buffer[256];
        ssize_t length;
        _data->Seek(0,SEEK_SET);

        while ((length = _data->Read(buffer, sizeof(buffer))) > 0)
                data->Write(buffer, length);

        return B_OK;
}


BPositionIO *
BSimpleMailAttachment::GetDecodedData()
{
        ParseNow();
        return _data;
}


status_t
BSimpleMailAttachment::SetDecodedDataAndDeleteWhenDone(BPositionIO *data)
{
        _raw_data = NULL;

        if (_we_own_data)
                delete _data;

        _data = data;
        _we_own_data = true;

        return B_OK;
}


status_t
BSimpleMailAttachment::SetDecodedData(BPositionIO *data)
{
        _raw_data = NULL;

        if (_we_own_data)
                delete _data;

        _data = data;
        _we_own_data = false;

        return B_OK;
}


status_t
BSimpleMailAttachment::SetDecodedData(const void *data, size_t length)
{
        _raw_data = NULL;

        if (_we_own_data)
                delete _data;

        _data = new BMemoryIO(data,length);
        _we_own_data = true;

        return B_OK;
}


void
BSimpleMailAttachment::SetEncoding(mail_encoding encoding)
{
        _encoding = encoding;

        const char *cte = NULL; //--Content Transfer Encoding
        switch (_encoding) {
                case base64:
                        cte = "base64";
                        break;
                case seven_bit:
                case no_encoding:
                        cte = "7bit";
                        break;
                case eight_bit:
                        cte = "8bit";
                        break;
                case uuencode:
                        cte = "uuencode";
                        break;
                case quoted_printable:
                        cte = "quoted-printable";
                        break;
                default:
                        cte = "bug-not-implemented";
                        break;
        }

        SetHeaderField("Content-Transfer-Encoding", cte);
}


mail_encoding
BSimpleMailAttachment::Encoding()
{
        return _encoding;
}


status_t
BSimpleMailAttachment::SetToRFC822(BPositionIO *data, size_t length,
        bool parseNow)
{
        //---------Massive memory squandering!---ALERT!----------
        if (_we_own_data)
                delete _data;

        off_t position = data->Position();
        BMailComponent::SetToRFC822(data, length, parseNow);

        // this actually happens...
        if (data->Position() - position > (off_t)length)
                return B_ERROR;

        length -= (data->Position() - position);

        _raw_data = data;
        _raw_length = length;
        _raw_offset = data->Position();

        BString encoding = HeaderField("Content-Transfer-Encoding");
        if (encoding.IFindFirst("base64") >= 0)
                _encoding = base64;
        else if (encoding.IFindFirst("quoted-printable") >= 0)
                _encoding = quoted_printable;
        else if (encoding.IFindFirst("uuencode") >= 0)
                _encoding = uuencode;
        else if (encoding.IFindFirst("7bit") >= 0)
                _encoding = seven_bit;
        else if (encoding.IFindFirst("8bit") >= 0)
                _encoding = eight_bit;
        else
                _encoding = no_encoding;

        if (parseNow)
                ParseNow();

        return B_OK;
}


void
BSimpleMailAttachment::ParseNow()
{
        if (_raw_data == NULL || _raw_length == 0)
                return;

        _raw_data->Seek(_raw_offset, SEEK_SET);

        char *src = (char *)malloc(_raw_length);
        if (src == NULL)
                return;

        size_t size = _raw_length;

        size = _raw_data->Read(src, _raw_length);

        BMallocIO *buffer = new BMallocIO;
        buffer->SetSize(size);
                // 8bit is *always* more efficient than an encoding, so the buffer
                // will *never* be larger than before

        size = decode(_encoding,(char *)(buffer->Buffer()),src,size,0);
        free(src);

        buffer->SetSize(size);

        _data = buffer;
        _we_own_data = true;

        _raw_data = NULL;

        return;
}


status_t
BSimpleMailAttachment::RenderToRFC822(BPositionIO *renderTo)
{
        ParseNow();
        BMailComponent::RenderToRFC822(renderTo);
        //---------Massive memory squandering!---ALERT!----------

        _data->Seek(0, SEEK_END);
        off_t size = _data->Position();
        char *src = (char *)malloc(size);
        if (src == NULL)
                return B_NO_MEMORY;

        MemoryDeleter sourceDeleter(src);

        _data->Seek(0, SEEK_SET);

        ssize_t read = _data->Read(src, size);
        if (read < B_OK)
                return read;

        // The encoded text will never be more than twice as large with any
        // conceivable encoding.  But just in case, there's a function call which
        // will tell us how much space is needed.
        ssize_t destSize = max_encoded_length(_encoding, read);
        if (destSize < B_OK) // Invalid encodings like uuencode rejected here.
                return destSize;
        char *dest = (char *)malloc(destSize);
        if (dest == NULL)
                return B_NO_MEMORY;

        MemoryDeleter destinationDeleter(dest);

        destSize = encode (_encoding, dest, src, read, false /* headerMode */);
        if (destSize < B_OK)
                return destSize;

        if (destSize > 0)
                read = renderTo->Write(dest, destSize);

        return read > 0 ? B_OK : read;
}


//      #pragma mark -


/*!     Supports and sends attributes.
*/
BAttributedMailAttachment::BAttributedMailAttachment()
        :
        fContainer(NULL),
        fStatus(B_NO_INIT),
        _data(NULL),
        _attributes_attach(NULL)
{
}


BAttributedMailAttachment::BAttributedMailAttachment(BFile *file,
        bool deleteWhenDone)
        :
        fContainer(NULL),
        _data(NULL),
        _attributes_attach(NULL)
{
        SetTo(file, deleteWhenDone);
}


BAttributedMailAttachment::BAttributedMailAttachment(entry_ref *ref)
        :
        fContainer(NULL),
        _data(NULL),
        _attributes_attach(NULL)
{
        SetTo(ref);
}


BAttributedMailAttachment::~BAttributedMailAttachment()
{
        // Our SimpleAttachments are deleted by fContainer
        delete fContainer;
}


status_t
BAttributedMailAttachment::Initialize()
{
        // _data & _attributes_attach will be deleted by the container
        delete fContainer;

        fContainer = new BMIMEMultipartMailContainer("++++++BFile++++++");

        _data = new BSimpleMailAttachment();
        fContainer->AddComponent(_data);

        _attributes_attach = new BSimpleMailAttachment();
        _attributes.MakeEmpty();
        _attributes_attach->SetHeaderField("Content-Type",
                "application/x-be_attribute; name=\"BeOS Attributes\"");
        fContainer->AddComponent(_attributes_attach);

        fContainer->SetHeaderField("Content-Type", "multipart/x-bfile");
        fContainer->SetHeaderField("Content-Disposition", "BMailAttachment");

        // also set the header fields of this component, in case someone asks
        SetHeaderField("Content-Type", "multipart/x-bfile");
        SetHeaderField("Content-Disposition", "BMailAttachment");

        return B_OK;
}


status_t
BAttributedMailAttachment::SetTo(BFile *file, bool deleteFileWhenDone)
{
        if (file == NULL)
                return fStatus = B_BAD_VALUE;

        if ((fStatus = Initialize()) < B_OK)
                return fStatus;

        _attributes << *file;

        if ((fStatus = _data->SetTo(file, deleteFileWhenDone)) < B_OK)
                return fStatus;

        // Set boundary

        // Also, we have the make up the boundary out of whole cloth
        // This is likely to give a completely random string
        BString boundary;
        boundary << "BFile--" << ((long)file ^ time(NULL)) << "-"
                << ~((long)file ^ (long)&fStatus ^ (long)&_attributes) << "--";
        fContainer->SetBoundary(boundary.String());

        return fStatus = B_OK;
}


status_t
BAttributedMailAttachment::SetTo(entry_ref *ref)
{
        if (ref == NULL)
                return fStatus = B_BAD_VALUE;

        if ((fStatus = Initialize()) < B_OK)
                return fStatus;

        BNode node(ref);
        if ((fStatus = node.InitCheck()) < B_OK)
                return fStatus;

        _attributes << node;

        if ((fStatus = _data->SetTo(ref)) < B_OK)
                return fStatus;

        // Set boundary

        // This is likely to give a completely random string
        BString boundary;
        char buffer[512];
        strcpy(buffer, ref->name);
        for (int32 i = strlen(buffer); i-- > 0;) {
                if (buffer[i] & 0x80)
                        buffer[i] = 'x';
                else if (buffer[i] == ' ' || buffer[i] == ':')
                        buffer[i] = '_';
        }
        buffer[32] = '\0';
        boundary << "BFile-" << buffer << "--" << ((long)_data ^ time(NULL))
                << "-" << ~((long)_data ^ (long)&buffer ^ (long)&_attributes)
                << "--";
        fContainer->SetBoundary(boundary.String());

        return fStatus = B_OK;
}


status_t
BAttributedMailAttachment::InitCheck()
{
        return fStatus;
}


void
BAttributedMailAttachment::SaveToDisk(BEntry *entry)
{
        BString path = "/tmp/";
        char name[B_FILE_NAME_LENGTH] = "";
        _data->FileName(name);
        path << name;

        BFile file(path.String(), B_READ_WRITE | B_CREATE_FILE);
        (BNode&)file << _attributes;
        _data->GetDecodedData(&file);
        file.Sync();

        entry->SetTo(path.String());
}


void
BAttributedMailAttachment::SetEncoding(mail_encoding encoding)
{
        _data->SetEncoding(encoding);
        if (_attributes_attach != NULL)
                _attributes_attach->SetEncoding(encoding);
}


mail_encoding
BAttributedMailAttachment::Encoding()
{
        return _data->Encoding();
}


status_t
BAttributedMailAttachment::FileName(char *name)
{
        return _data->FileName(name);
}


void
BAttributedMailAttachment::SetFileName(const char *name)
{
        _data->SetFileName(name);
}


status_t
BAttributedMailAttachment::GetDecodedData(BPositionIO *data)
{
        BNode *node = dynamic_cast<BNode *>(data);
        if (node != NULL)
                *node << _attributes;

        _data->GetDecodedData(data);
        return B_OK;
}


status_t
BAttributedMailAttachment::SetDecodedData(BPositionIO *data)
{
        BNode *node = dynamic_cast<BNode *>(data);
        if (node != NULL)
                _attributes << *node;

        _data->SetDecodedData(data);
        return B_OK;
}


status_t
BAttributedMailAttachment::SetToRFC822(BPositionIO *data, size_t length,
        bool parseNow)
{
        status_t err = Initialize();
        if (err < B_OK)
                return err;

        err = fContainer->SetToRFC822(data, length, parseNow);
        if (err < B_OK)
                return err;

        BMimeType type;
        fContainer->MIMEType(&type);
        if (strcmp(type.Type(), "multipart/x-bfile") != 0)
                return B_BAD_TYPE;

        // get data and attributes
        if ((_data = dynamic_cast<BSimpleMailAttachment *>(
                        fContainer->GetComponent(0))) == NULL)
                return B_BAD_VALUE;

        if (parseNow) {
                // Force it to make a copy of the data. Needed for forwarding
                // messages hack.
                _data->GetDecodedData();
        }

        if ((_attributes_attach = dynamic_cast<BSimpleMailAttachment *>(
                                fContainer->GetComponent(1))) == NULL
                || _attributes_attach->GetDecodedData() == NULL)
                return B_OK;

        // Convert the attribute binary attachment into a convenient easy to use
        // BMessage.

        int32 len
                = ((BMallocIO *)(_attributes_attach->GetDecodedData()))->BufferLength();
        char *start = (char *)malloc(len);
        if (start == NULL)
                return B_NO_MEMORY;

        MemoryDeleter deleter(start);

        if (_attributes_attach->GetDecodedData()->ReadAt(0, start, len) < len)
                return B_IO_ERROR;

        int32 index = 0;
        while (index < len) {
                char *name = &start[index];
                index += strlen(name) + 1;

                type_code code;
                memcpy(&code, &start[index], sizeof(type_code));
                code = B_BENDIAN_TO_HOST_INT32(code);
                index += sizeof(type_code);

                int64 buf_length;
                memcpy(&buf_length, &start[index], sizeof(buf_length));
                buf_length = B_BENDIAN_TO_HOST_INT64(buf_length);
                index += sizeof(buf_length);

                swap_data(code, &start[index], buf_length, B_SWAP_BENDIAN_TO_HOST);
                _attributes.AddData(name, code, &start[index], buf_length);
                index += buf_length;
        }

        return B_OK;
}


status_t
BAttributedMailAttachment::RenderToRFC822(BPositionIO *renderTo)
{
        BMallocIO *io = new BMallocIO;

#if defined(HAIKU_TARGET_PLATFORM_DANO)
        const
#endif
        char *name;
        type_code type;
        for (int32 i = 0; _attributes.GetInfo(B_ANY_TYPE, i, &name, &type) == B_OK;
                        i++) {
                const void *data;
                ssize_t dataLen;
                _attributes.FindData(name, type, &data, &dataLen);
                io->Write(name, strlen(name) + 1);

                type_code swappedType = B_HOST_TO_BENDIAN_INT32(type);
                io->Write(&swappedType, sizeof(type_code));

                int64 length, swapped;
                length = dataLen;
                swapped = B_HOST_TO_BENDIAN_INT64(length);
                io->Write(&swapped,sizeof(int64));

                void *buffer = malloc(dataLen);
                if (buffer == NULL) {
                        delete io;
                        return B_NO_MEMORY;
                }
                memcpy(buffer, data, dataLen);
                swap_data(type, buffer, dataLen, B_SWAP_HOST_TO_BENDIAN);
                io->Write(buffer, dataLen);
                free(buffer);
        }
        if (_attributes_attach == NULL)
                _attributes_attach = new BSimpleMailAttachment;

        _attributes_attach->SetDecodedDataAndDeleteWhenDone(io);

        return fContainer->RenderToRFC822(renderTo);
}


status_t
BAttributedMailAttachment::MIMEType(BMimeType *mime)
{
        return _data->MIMEType(mime);
}


// #pragma mark - The reserved function stubs


void BMailAttachment::_ReservedAttachment1() {}
void BMailAttachment::_ReservedAttachment2() {}
void BMailAttachment::_ReservedAttachment3() {}
void BMailAttachment::_ReservedAttachment4() {}

void BSimpleMailAttachment::_ReservedSimple1() {}
void BSimpleMailAttachment::_ReservedSimple2() {}
void BSimpleMailAttachment::_ReservedSimple3() {}

void BAttributedMailAttachment::_ReservedAttributed1() {}
void BAttributedMailAttachment::_ReservedAttributed2() {}
void BAttributedMailAttachment::_ReservedAttributed3() {}