//=============================================================================
// File:       body.cpp
// Contents:   Definitions for DwBody
// Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
// WWW:        http://www.fwb.gulf.net/~dwsauder/mimepp.html
//
// 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 <assert.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <iostream>
#include <mimelib/string.h>
#include <mimelib/headers.h>
#include <mimelib/bodypart.h>
#include <mimelib/body.h>
#include <mimelib/message.h>
#include <mimelib/mediatyp.h>
#include <mimelib/enum.h>

enum {
    kParseSuccess,
    kParseFail
};


struct DwBodyPartStr {
    DwBodyPartStr(const DwString& aStr) : mString(aStr), mNext(0) {}
    DwString mString;
    DwBodyPartStr* mNext;
};


class DwBodyParser {
    friend class DwBody;
public:
    ~DwBodyParser();
private:
    DwBodyParser(const DwString& aStr, const DwString& aBoundaryStr);
    const DwString& Preamble() const     { return mPreamble; }
    const DwString& Epilogue() const     { return mEpilogue; }
    DwBodyPartStr* FirstBodyPart() const { return mFirstBodyPartStr; }
    int Parse();
    int FindBoundary(size_t aStartPos, size_t* aBoundaryStart,
        size_t* aBoundaryEnd, size_t* isFinal) const;
    void AddPart(size_t start, size_t len);
    void DeleteParts();
    const DwString mString;
    const DwString mBoundary;
    DwString mPreamble;
    DwBodyPartStr* mFirstBodyPartStr;
    DwString mEpilogue;
};


DwBodyParser::DwBodyParser(const DwString& aStr, const DwString& aBoundary)
  : mString(aStr), mBoundary(aBoundary)
{
    mFirstBodyPartStr = 0;
    Parse();
}


DwBodyParser::~DwBodyParser()
{
    DeleteParts();
}


int DwBodyParser::Parse()
{
    DeleteParts();
    // Find the preamble
    size_t pos = 0;
    size_t boundaryStart, boundaryEnd, isFinal;
    int result;
    result = FindBoundary(pos, &boundaryStart, &boundaryEnd, &isFinal);
    if (result == kParseFail) {
        mPreamble = mEpilogue = "";
        mFirstBodyPartStr = 0;
        return kParseFail;
    }
    int start = pos;
    int len = boundaryStart - pos;
    mPreamble = mString.substr(start, len);
    if ( boundaryStart < mString.size() && mString[boundaryStart] != '-' )
      mPreamble += DW_EOL; // contrary to normal behaviour of
			   // DwBody::Parse(), we _do_ want a newline
			   // before the first boundary here. This is
			   // necessary since FindBoundary() can't
			   // make up it's mind on where the boundary
			   // starts - on the leading \n or the first
			   // '-'..

    // Find the body parts
    pos = boundaryEnd;
    while (1) {
        result = FindBoundary(pos, &boundaryStart, &boundaryEnd, &isFinal);
        // NOTE: For enhanced fault tolerance we *accept* a missing last
        //       boundary.
        //       If no last boundary is found (but at leat a first one was
        //       there) we just assume the end of the text ebing the end
        //       of the last part.
        //       By doing so we can safely parse some buggy MS Outlook
        //       clients' messages.                      (khz, 12.06.2002)
        start = pos;

        if (result == kParseFail) {
            isFinal = true;
            len = mString.length() - pos;
        } else {
            len = boundaryStart - pos;
        }

        AddPart(start, len);

        if (result == kParseFail) {
            pos = mString.length();
        } else {
            pos = boundaryEnd;
        }

        if (isFinal) {
            break;
        }
    }

    // Find the epilogue
    start = pos;
    len = mString.length() - pos;
    if( len )
        mEpilogue = mString.substr(start, len);

    return kParseSuccess;
}

// checks whether [cur,end[ matches -*[\r\t ]*(\n|$)
static bool isOnlyWhiteSpaceOrDashesUntilEndOfLine( const char * cur, const char * end ) {
  bool dashesStillAllowed = true;

  while ( cur < end )
    switch( *cur ) {
    case ' ':
    case '\t':
    case '\r':
      dashesStillAllowed = false;
      ++cur;
      continue;
    case '\n':
      return true;
    case '-':
      if ( !dashesStillAllowed )
	return false;
      ++cur;
      continue;
    default:
      return false;
    }
  // end of buffer is ok, too:
  return true;
}

int DwBodyParser::FindBoundary(size_t aStartPos, size_t* aBoundaryStart,
    size_t* aBoundaryEnd, size_t* aIsFinal) const
{
    // Assume the starting position is the beginning of a line
    const char* buf = mString.data();
    size_t pos = aStartPos;
    size_t endPos = mString.length();
    size_t blen = mBoundary.length();
    // Search for the first boundary.
    // The leading CR LF ('\n') is part of the boundary, but if there is
    // no preamble, there may be no leading CR LF ('\n').
    // The case of no leading CR LF ('\n') is a special case that will occur
    // only when '-' is the first character of the body.
    if (buf[pos] == '-'
        && pos+blen+1 < endPos
        && buf[pos+1] == '-'
        && strncmp(&buf[pos+2], mBoundary.data(), blen) == 0
	&& isOnlyWhiteSpaceOrDashesUntilEndOfLine( buf + pos + blen + 2, buf + endPos ) ) {

        *aBoundaryStart = pos;
        pos += blen + 2;
        // Check for final boundary
        if (pos+1 < endPos
            && buf[pos] == '-'
            && buf[pos+1] == '-') {

            pos += 2;
            *aIsFinal = 1;
        }
        else {
            *aIsFinal = 0;
        }
        // Advance position past end of line
        while (pos < endPos) {
            if (buf[pos] == '\n') {
                ++pos;
                break;
            }
            ++pos;
        }
        *aBoundaryEnd = pos;
        return kParseSuccess;
    }
    int isFound = 0;
    while (pos+blen+2 < endPos) {
        // Case of leading LF
        if (buf[pos] == '\n'
            && buf[pos+1] == '-'
            && buf[pos+2] == '-'
            && strncmp(&buf[pos+3], mBoundary.data(), blen) == 0
	    && isOnlyWhiteSpaceOrDashesUntilEndOfLine( buf + pos + blen + 3, buf + endPos ) ) {

            *aBoundaryStart = pos;
            pos += blen + 3;
            isFound = 1;
        }
        // Case of leading CR LF
        else if (buf[pos] == '\r'
            && buf[pos+1] == '\n'
            && buf[pos+2] == '-'
            && pos+blen+3 < endPos
            && buf[pos+3] == '-'
            && strncmp(&buf[pos+4], mBoundary.data(), blen) == 0
	    && isOnlyWhiteSpaceOrDashesUntilEndOfLine( buf + pos + blen + 4, buf + endPos ) ) {

            *aBoundaryStart = pos;
            pos += blen + 4;
            isFound = 1;
        }
        if (isFound) {
            // Check for final boundary
            if (pos < endPos
                && buf[pos] == '-') {

                // NOTE: Since we must be fault tolerant for being able to
                //       understand messaged that were damaged during
                //       transportation we now accept final boundaries
                //       ending with "-" instead of "--".
                //       (khz, 12.06.2002)
                pos += 1;
                *aIsFinal = 1;

                // if there *is* the 2nd '-' we of course process it
                if (pos+1 < endPos
                    && buf[pos+1] == '-') {
                    pos += 1;
                }
            }
            else {
                *aIsFinal = 0;
            }
            // Advance position past end of line
            while (pos < endPos) {
                if (buf[pos] == '\n') {
                    ++pos;
                    break;
                }
                ++pos;
            }
            *aBoundaryEnd = pos;
            return kParseSuccess;
        }
        ++pos;
    }
    // Exceptional case: no boundary found
    *aBoundaryStart = *aBoundaryEnd = mString.length();
    *aIsFinal = 1;
    return kParseFail;
}


void DwBodyParser::AddPart(size_t start, size_t len)
{
    DwBodyPartStr* toAdd = new DwBodyPartStr(mString.substr(start, len));
    if (toAdd != 0) {
        DwBodyPartStr* curr = mFirstBodyPartStr;
        if (curr == 0) {
            mFirstBodyPartStr = toAdd;
            return;
        }
        while (curr->mNext != 0) {
            curr = curr->mNext;
        }
        curr->mNext = toAdd;
    }
}


void DwBodyParser::DeleteParts()
{
    DwBodyPartStr* curr = mFirstBodyPartStr;
    while (curr) {
        DwBodyPartStr* next = curr->mNext;
        delete curr;
        curr = next;
    }
    mFirstBodyPartStr = 0;
}


//==========================================================================


const char* const DwBody::sClassName = "DwBody";


DwBody* (*DwBody::sNewBody)(const DwString&, DwMessageComponent*) = 0;


DwBody* DwBody::NewBody(const DwString& aStr, DwMessageComponent* aParent)
{
    if (sNewBody) {
        DwBody* newBody = sNewBody(aStr, aParent);
        //if( newBody )
        //    newBody->mFirstBodyPart = 0;
        return newBody;
    }
    else {
        return new DwBody(aStr, aParent);
    }
}


DwBody::DwBody()
{
    mFirstBodyPart = 0;
    mMessage = 0;
    mClassId = kCidBody;
    mClassName = sClassName;
}


DwBody::DwBody(const DwBody& aBody)
  : DwMessageComponent(aBody),
    mBoundaryStr(aBody.mBoundaryStr),
    mPreamble(aBody.mPreamble),
    mEpilogue(aBody.mEpilogue)
{
    mFirstBodyPart = 0;
    const DwBodyPart* firstPart = aBody.mFirstBodyPart;
    if (firstPart) {
        CopyBodyParts(firstPart);
    }
    mMessage = 0;
    const DwMessage* message = aBody.mMessage;
    if (message) {
        DwMessage* msg = (DwMessage*) message->Clone();
        _SetMessage(msg);
    }
    mClassId = kCidBody;
    mClassName = sClassName;
}


DwBody::DwBody(const DwString& aStr, DwMessageComponent* aParent)
  : DwMessageComponent(aStr, aParent)
{
    mFirstBodyPart = 0;
    mMessage = 0;
    mClassId = kCidBody;
    mClassName = sClassName;
}


DwBody::~DwBody()
{
    if (mFirstBodyPart) {
        DeleteBodyParts();
    }
    if (mMessage) {
        delete mMessage;
    }
}


const DwBody& DwBody::operator = (const DwBody& aBody)
{
    if (this == &aBody) return *this;
    mBoundaryStr = aBody.mBoundaryStr;
    mPreamble    = aBody.mPreamble;
    mEpilogue    = aBody.mEpilogue;
    if (mFirstBodyPart) {
        DeleteBodyParts();
    }
    const DwBodyPart* firstPart = aBody.FirstBodyPart();
    if (firstPart) {
        CopyBodyParts(firstPart);
    }
    if (mMessage) {
        delete mMessage;
    }
    const DwMessage* message = aBody.Message();
    if (message) {
        DwMessage* msg = (DwMessage*) message->Clone();
        _SetMessage(msg);
    }
    if (mParent) {
        mParent->SetModified();
    }
    return *this;
}


void DwBody::Parse()
{
    mIsModified = 0;
    // Only types "multipart" and "message" need to be parsed, and
    // we cannot determine the type if there is no header.
    if (!mParent) {
        return;
    }
    // Get the content type from the headers
    DwEntity* entity = (DwEntity*) mParent;
    if (entity->Headers().HasContentType()) {
        const DwMediaType& contentType = entity->Headers().ContentType();
        int type = contentType.Type();
        int subtype = contentType.Subtype();
        if (type == DwMime::kTypeMultipart) {
            mBoundaryStr = contentType.Boundary();
            // Now parse body into body parts
            DwBodyParser parser(mString, mBoundaryStr);
            mPreamble = parser.Preamble();
            mEpilogue = parser.Epilogue();
            DwBodyPartStr* partStr = parser.FirstBodyPart();
            while (partStr) {
                DwBodyPart* part =
                    DwBodyPart::NewBodyPart(partStr->mString, this);
                part->Parse();
                _AddBodyPart(part);
                partStr = partStr->mNext;
            }
        }
        else if (type == DwMime::kTypeMessage &&
                 subtype == DwMime::kSubtypeRfc822) {
            if (mMessage)
              mMessage->FromString(mString);
            else
              mMessage = DwMessage::NewMessage(mString, this);
            mMessage->Parse();
        }
    }
}


void DwBody::Assemble()
{
    if (!mIsModified) return;
    if (!mFirstBodyPart && !mMessage) return;
    if (!mParent) return;

    DwEntity* entity = (DwEntity*) mParent;
    /*
    DwString partStr;
    */
    const DwMediaType& contentType = entity->Headers().ContentType();
    int type = contentType.Type();
    int subtype = contentType.Subtype();
    if (type == DwMime::kTypeMultipart) {
        /*
        int len;
        */
        mBoundaryStr = contentType.Boundary();
        mString = "";
        mString += mPreamble;
        DwBodyPart* part = mFirstBodyPart;
        while (part) {
            part->Assemble();
            /*
            partStr = part->AsString();
            len = mString.length();
            if( ! (    (    (1 < len)
                         && ('\n' == mString.at(len-1) )
                         && ('\n' == mString.at(len-2) ) )
                    || (    (2 < len)
                         && ('\n' == mString.at(len-1) )
                         && ('\r' == mString.at(len-2) )
                         && ('\n' == mString.at(len-3) ) ) ) )
            */
	    if ( part != mFirstBodyPart )
                mString += DW_EOL;
            mString += "--";
            mString += mBoundaryStr;
            /*
            len = partStr.length();
            if( ! (    (0 < len)
                    && (    ('\n' == partStr.at(0) )
                         || ('\r' == partStr.at(0) ) ) ) )
            */
                mString += DW_EOL;
            /*
            mString += partStr;
            */
            mString += part->AsString();
            part = part->Next();
        }
        /*
        if( ! (    (    (1 < len)
                    && ('\n' == mString.at(len-1) )
                    && ('\n' == mString.at(len-2) ) )
                || (    (2 < len)
                    && ('\n' == mString.at(len-1) )
                    && ('\r' == mString.at(len-2) )
                    && ('\n' == mString.at(len-3) ) ) ) )
        */
            mString += DW_EOL;
        mString += "--";
        mString += mBoundaryStr;
        mString += "--";
        mString += DW_EOL;
        mString += mEpilogue;
        mIsModified = 0;
    }
    else if (type == DwMime::kTypeMessage &&
             subtype == DwMime::kSubtypeRfc822 &&
             mMessage) {
        mMessage->Assemble();
        mString = mMessage->AsString();
    }
    else {
        // Empty block
    }
}


DwMessageComponent* DwBody::Clone() const
{
    return new DwBody(*this);
}


DwBodyPart* DwBody::FirstBodyPart() const
{
    return mFirstBodyPart;
}


void DwBody::AddBodyPart(DwBodyPart* aPart)
{
    _AddBodyPart(aPart);
    SetModified();
}

void DwBody::RemoveBodyPart(DwBodyPart* aPart)
{
    _RemoveBodyPart(aPart);
    SetModified();
}


DwMessage* DwBody::Message() const
{
    return mMessage;
}


void DwBody::SetMessage(DwMessage* aMessage)
{
    _SetMessage(aMessage);
    SetModified();
}


void DwBody::_AddBodyPart(DwBodyPart* aPart)
{
    aPart->SetParent(this);
    if (!mFirstBodyPart) {
        mFirstBodyPart = aPart;
        return;
    }
    DwBodyPart* part = mFirstBodyPart;
    while (part->Next()) {
        part = part->Next();
    }
    part->SetNext(aPart);
}

void DwBody::_RemoveBodyPart(DwBodyPart* aPart)
{
    if ( aPart->Parent() != this )
        return; // caller error
    if ( !mFirstBodyPart )
        return; // impossible
    if ( mFirstBodyPart == aPart ) {
        mFirstBodyPart = mFirstBodyPart->Next();
        return;
    }
    DwBodyPart* part = mFirstBodyPart;
    while (part->Next()) {
        if ( part->Next() == aPart ) {
            part->SetNext(aPart->Next());
            break;
        }
        part = part->Next();
    }
}


void DwBody::_SetMessage(DwMessage* aMessage)
{
    aMessage->SetParent(this);
    if (mMessage && mMessage != aMessage) {
        delete mMessage;
    }
    mMessage = aMessage;
}


void DwBody::DeleteBodyParts()
{
    DwBodyPart* part = mFirstBodyPart;
    while (part) {
        DwBodyPart* nextPart = part->Next();
        delete part;
        part = nextPart;
    }
    mFirstBodyPart = 0;
}


void DwBody::CopyBodyParts(const DwBodyPart* aFirst)
{
    const DwBodyPart* part = aFirst;
    while (part) {
        DwBodyPart* newPart = (DwBodyPart*) part->Clone();
        AddBodyPart(newPart);
        part = part->Next();
    }
}


#if defined(DW_DEBUG_VERSION)
void DwBody::PrintDebugInfo(std::ostream& aStrm, int /*aDepth*/) const
{
    aStrm <<
    "------------------ Debug info for DwBody class -----------------\n";
    _PrintDebugInfo(aStrm);
}
#else
void DwBody::PrintDebugInfo(std::ostream&, int) const {}
#endif // defined(DW_DEBUG_VERSION)


#if defined(DW_DEBUG_VERSION)
void DwBody::_PrintDebugInfo(std::ostream& aStrm) const
{
    DwMessageComponent::_PrintDebugInfo(aStrm);
    aStrm << "Boundary:         " << mBoundaryStr << '\n';
    aStrm << "Preamble:         " << mPreamble << '\n';
    aStrm << "Epilogue:         " << mEpilogue << '\n';
    aStrm << "Body Parts:       ";
    int count = 0;
    DwBodyPart* bodyPart = mFirstBodyPart;
    if (bodyPart) {
        while (bodyPart) {
            if (count > 0) aStrm << ' ';
            aStrm << bodyPart->ObjectId();
            bodyPart = (DwBodyPart*) bodyPart->Next();
            ++count;
        }
        aStrm << '\n';
    }
    else {
        aStrm << "(none)\n";
    }
    aStrm << "Message:       ";
    if (mMessage) {
        aStrm << mMessage->ObjectId() << '\n';
    }
    else {
        aStrm << "(none)\n";
    }
}
#else
void DwBody::_PrintDebugInfo(std::ostream& ) const {}
#endif // defined(DW_DEBUG_VERSION)


void DwBody::CheckInvariants() const
{
#if defined(DW_DEBUG_VERSION)
    DwMessageComponent::CheckInvariants();
    mBoundaryStr.CheckInvariants();
    mPreamble.CheckInvariants();
    mEpilogue.CheckInvariants();
    DwBodyPart* bodyPart = mFirstBodyPart;
    while (bodyPart) {
        bodyPart->CheckInvariants();
        bodyPart = (DwBodyPart*) bodyPart->Next();
    }
    if (mMessage) {
        mMessage->CheckInvariants();
    }
#endif // defined(DW_DEBUG_VERSION)
}
