/***************************************************************************
                          mimeheader.cpp  -  description
                             -------------------
    begin                : Fri Oct 20 2000
    copyright            : (C) 2000 by Sven Carstens
    email                : s.carstens@gmx.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "mimeheader.h"
#include "mimehdrline.h"
#include "mailheader.h"
#include "rfcdecoder.h"

#include <tqregexp.h>

// #include <iostream.h>
#include <tdeglobal.h>
#include <kinstance.h>
#include <kiconloader.h>
#include <kmimetype.h>
#include <kmimemagic.h>
#include <kmdcodec.h>
#include <kdebug.h>

mimeHeader::mimeHeader ():
typeList (17, false), dispositionList (17, false)
{
  // Case insensitive hashes are killing us.  Also are they too small?
  originalHdrLines.setAutoDelete (true);
  additionalHdrLines.setAutoDelete (false); // is also in original lines
  nestedParts.setAutoDelete (true);
  typeList.setAutoDelete (true);
  dispositionList.setAutoDelete (true);
  nestedMessage = NULL;
  contentLength = 0;
  contentType = "application/octet-stream";
}

mimeHeader::~mimeHeader ()
{
}

/*
TQPtrList<mimeHeader> mimeHeader::getAllParts()
{
	TQPtrList<mimeHeader> retVal;

	// caller is responsible for clearing
	retVal.setAutoDelete( false );
	nestedParts.setAutoDelete( false );

	// shallow copy
	retVal = nestedParts;

	// can't have duplicate pointers
	nestedParts.clear();

	// restore initial state
	nestedParts.setAutoDelete( true );

	return retVal;
} */

void
mimeHeader::addHdrLine (mimeHdrLine * aHdrLine)
{
  mimeHdrLine *addLine = new mimeHdrLine (aHdrLine);
  if (addLine)
  {
    originalHdrLines.append (addLine);
    if (tqstrnicmp (addLine->getLabel (), "Content-", 8))
    {
      additionalHdrLines.append (addLine);
    }
    else
    {
      int skip;
      const char *aCStr = addLine->getValue ().data ();
      TQDict < TQString > *aList = 0;

      skip = mimeHdrLine::parseSeparator (';', aCStr);
      if (skip > 0)
      {
        int cut = 0;
        if (skip >= 2)
        {
          if (aCStr[skip - 1] == '\r')
            cut++;
          if (aCStr[skip - 1] == '\n')
            cut++;
          if (aCStr[skip - 2] == '\r')
            cut++;
          if (aCStr[skip - 1] == ';')
            cut++;
        }
        TQCString mimeValue = TQCString (aCStr, skip - cut + 1);  // cutting of one because of 0x00


        if (!tqstricmp (addLine->getLabel (), "Content-Disposition"))
        {
          aList = &dispositionList;
          _contentDisposition = mimeValue;
        }
        else if (!tqstricmp (addLine->getLabel (), "Content-Type"))
        {
          aList = &typeList;
          contentType = mimeValue;
        }
        else
          if (!tqstricmp (addLine->getLabel (), "Content-Transfer-Encoding"))
        {
          contentEncoding = mimeValue;
        }
        else if (!tqstricmp (addLine->getLabel (), "Content-ID"))
        {
          contentID = mimeValue;
        }
        else if (!tqstricmp (addLine->getLabel (), "Content-Description"))
        {
          _contentDescription = mimeValue;
        }
        else if (!tqstricmp (addLine->getLabel (), "Content-MD5"))
        {
          contentMD5 = mimeValue;
        }
        else if (!tqstricmp (addLine->getLabel (), "Content-Length"))
        {
          contentLength = mimeValue.toULong ();
        }
        else
        {
          additionalHdrLines.append (addLine);
        }
//        cout << addLine->getLabel().data() << ": '" << mimeValue.data() << "'" << endl;

        aCStr += skip;
        while ((skip = mimeHdrLine::parseSeparator (';', aCStr)))
        {
          if (skip > 0)
          {
            addParameter (TQCString (aCStr, skip).simplifyWhiteSpace(), aList);
//            cout << "-- '" << aParm.data() << "'" << endl;
            mimeValue = TQCString (addLine->getValue ().data (), skip);
            aCStr += skip;
          }
          else
            break;
        }
      }
    }
  }
}

void
mimeHeader::addParameter (const TQCString& aParameter, TQDict < TQString > *aList)
{
  if ( !aList )
    return;

  TQString *aValue;
  TQCString aLabel;
  int pos = aParameter.find ('=');
//  cout << aParameter.left(pos).data();
  aValue = new TQString ();
  aValue->setLatin1 (aParameter.right (aParameter.length () - pos - 1));
  aLabel = aParameter.left (pos);
  if ((*aValue)[0] == '"')
    *aValue = aValue->mid (1, aValue->length () - 2);

  aList->insert (aLabel, aValue);
//  cout << "=" << aValue->data() << endl;
}

TQString
mimeHeader::getDispositionParm (const TQCString& aStr)
{
  return getParameter (aStr, &dispositionList);
}

TQString
mimeHeader::getTypeParm (const TQCString& aStr)
{
  return getParameter (aStr, &typeList);
}

void
mimeHeader::setDispositionParm (const TQCString& aLabel, const TQString& aValue)
{
  setParameter (aLabel, aValue, &dispositionList);
  return;
}

void
mimeHeader::setTypeParm (const TQCString& aLabel, const TQString& aValue)
{
  setParameter (aLabel, aValue, &typeList);
}

TQDictIterator < TQString > mimeHeader::getDispositionIterator ()
{
  return TQDictIterator < TQString > (dispositionList);
}

TQDictIterator < TQString > mimeHeader::getTypeIterator ()
{
  return TQDictIterator < TQString > (typeList);
}

TQPtrListIterator < mimeHdrLine > mimeHeader::getOriginalIterator ()
{
  return TQPtrListIterator < mimeHdrLine > (originalHdrLines);
}

TQPtrListIterator < mimeHdrLine > mimeHeader::getAdditionalIterator ()
{
  return TQPtrListIterator < mimeHdrLine > (additionalHdrLines);
}

void
mimeHeader::outputHeader (mimeIO & useIO)
{
  if (!getDisposition ().isEmpty ())
  {
    useIO.outputMimeLine (TQCString ("Content-Disposition: ")
                          + getDisposition ()
                          + outputParameter (&dispositionList));
  }

  if (!getType ().isEmpty ())
  {
    useIO.outputMimeLine (TQCString ("Content-Type: ")
                          + getType () + outputParameter (&typeList));
  }
  if (!getDescription ().isEmpty ())
    useIO.outputMimeLine (TQCString ("Content-Description: ") +
                          getDescription ());
  if (!getID ().isEmpty ())
    useIO.outputMimeLine (TQCString ("Content-ID: ") + getID ());
  if (!getMD5 ().isEmpty ())
    useIO.outputMimeLine (TQCString ("Content-MD5: ") + getMD5 ());
  if (!getEncoding ().isEmpty ())
    useIO.outputMimeLine (TQCString ("Content-Transfer-Encoding: ") +
                          getEncoding ());

  TQPtrListIterator < mimeHdrLine > ait = getAdditionalIterator ();
  while (ait.current ())
  {
    useIO.outputMimeLine (ait.current ()->getLabel () + ": " +
                          ait.current ()->getValue ());
    ++ait;
  }
  useIO.outputMimeLine (TQCString (""));
}

TQString
mimeHeader::getParameter (const TQCString& aStr, TQDict < TQString > *aDict)
{
  TQString retVal, *found;
  if (aDict)
  {
    //see if it is a normal parameter
    found = aDict->find (aStr);
    if (!found)
    {
      //might be a continuated or encoded parameter
      found = aDict->find (aStr + "*");
      if (!found)
      {
        //continuated parameter
        TQString decoded, encoded;
        int part = 0;

        do
        {
          TQCString search;
          search.setNum (part);
          search = aStr + "*" + search;
          found = aDict->find (search);
          if (!found)
          {
            found = aDict->find (search + "*");
            if (found)
              encoded += rfcDecoder::encodeRFC2231String (*found);
          }
          else
          {
            encoded += *found;
          }
          part++;
        }
        while (found);
        if (encoded.find ('\'') >= 0)
        {
          retVal = rfcDecoder::decodeRFC2231String (encoded.local8Bit ());
        }
        else
        {
          retVal =
            rfcDecoder::decodeRFC2231String (TQCString ("''") +
                                             encoded.local8Bit ());
        }
      }
      else
      {
        //simple encoded parameter
        retVal = rfcDecoder::decodeRFC2231String (found->local8Bit ());
      }
    }
    else
    {
      retVal = *found;
    }
  }
  return retVal;
}

void
mimeHeader::setParameter (const TQCString& aLabel, const TQString& aValue,
                          TQDict < TQString > *aDict)
{
  bool encoded = true;
  uint vlen, llen;
  TQString val = aValue;

  if (aDict)
  {

    //see if it needs to get encoded
    if (encoded && aLabel.find ('*') == -1)
    {
      val = rfcDecoder::encodeRFC2231String (aValue);
    }
    //kdDebug(7116) << "mimeHeader::setParameter() - val = '" << val << "'" << endl;
    //see if it needs to be truncated
    vlen = val.length();
    llen = aLabel.length();
    if (vlen + llen + 4 > 80 && llen < 80 - 8 - 2 )
    {
      const int limit = 80 - 8 - 2 - (int)llen;
      // the -2 is there to allow extending the length of a part of val
      // by 1 or 2 in order to prevent an encoded character from being
      // split in half
      int i = 0;
      TQString shortValue;
      TQCString shortLabel;

      while (!val.isEmpty ())
      {
        int partLen; // the length of the next part of the value
        if ( limit >= int(vlen) ) {
          // the rest of the value fits completely into one continued header
          partLen = vlen;
        }
        else {
          partLen = limit;
          // make sure that we don't split an encoded char in half
          if ( val[partLen-1] == '%' ) {
            partLen += 2;
          }
          else if ( partLen > 1 && val[partLen-2] == '%' ) {
            partLen += 1;
          }
          // make sure partLen does not exceed vlen (could happen in case of
          // an incomplete encoded char)
          if ( partLen > int(vlen) ) {
            partLen = vlen;
          }
        }
        shortValue = val.left( partLen );
        shortLabel.setNum (i);
        shortLabel = aLabel + "*" + shortLabel;
        val = val.right( vlen - partLen );
        vlen = vlen - partLen;
        if (encoded)
        {
          if (i == 0)
          {
            shortValue = "''" + shortValue;
          }
          shortLabel += "*";
        }
        //kdDebug(7116) << "mimeHeader::setParameter() - shortLabel = '" << shortLabel << "'" << endl;
        //kdDebug(7116) << "mimeHeader::setParameter() - shortValue = '" << shortValue << "'" << endl;
        //kdDebug(7116) << "mimeHeader::setParameter() - val        = '" << val << "'" << endl;
        aDict->insert (shortLabel, new TQString (shortValue));
        i++;
      }
    }
    else
    {
      aDict->insert (aLabel, new TQString (val));
    }
  }
}

TQCString
mimeHeader::outputParameter (TQDict < TQString > *aDict)
{
  TQCString retVal;
  if (aDict)
  {
    TQDictIterator < TQString > it (*aDict);
    while (it.current ())
    {
      retVal += (";\n\t" + it.currentKey () + "=").latin1 ();
      if (it.current ()->find (' ') > 0 || it.current ()->find (';') > 0)
      {
        retVal += '"' + it.current ()->utf8 () + '"';
      }
      else
      {
        retVal += it.current ()->utf8 ();
      }
      // << it.current()->utf8() << "'";
      ++it;
    }
    retVal += "\n";
  }
  return retVal;
}

void
mimeHeader::outputPart (mimeIO & useIO)
{
  TQPtrListIterator < mimeHeader > nestedParts = getNestedIterator ();
  TQCString boundary;
  if (!getTypeParm ("boundary").isEmpty ())
    boundary = getTypeParm ("boundary").latin1 ();

  outputHeader (useIO);
  if (!getPreBody ().isEmpty ())
    useIO.outputMimeLine (getPreBody ());
  if (getNestedMessage ())
    getNestedMessage ()->outputPart (useIO);
  while (nestedParts.current ())
  {
    if (!boundary.isEmpty ())
      useIO.outputMimeLine ("--" + boundary);
    nestedParts.current ()->outputPart (useIO);
    ++nestedParts;
  }
  if (!boundary.isEmpty ())
    useIO.outputMimeLine ("--" + boundary + "--");
  if (!getPostBody ().isEmpty ())
    useIO.outputMimeLine (getPostBody ());
}

int
mimeHeader::parsePart (mimeIO & useIO, const TQString& boundary)
{
  int retVal = 0;
  bool mbox = false;
  TQCString preNested, postNested;
  mbox = parseHeader (useIO);

  kdDebug(7116) << "mimeHeader::parsePart - parsing part '" << getType () << "'" << endl;
  if (!tqstrnicmp (getType (), "Multipart", 9))
  {
    retVal = parseBody (useIO, preNested, getTypeParm ("boundary"));  //this is a message in mime format stuff
    setPreBody (preNested);
    int localRetVal;
    do
    {
      mimeHeader *aHeader = new mimeHeader;

      // set default type for multipart/digest
      if (!tqstrnicmp (getType (), "Multipart/Digest", 16))
        aHeader->setType ("Message/RFC822");

      localRetVal = aHeader->parsePart (useIO, getTypeParm ("boundary"));
      addNestedPart (aHeader);
    }
    while (localRetVal);        //get nested stuff
  }
  if (!tqstrnicmp (getType (), "Message/RFC822", 14))
  {
    mailHeader *msgHeader = new mailHeader;
    retVal = msgHeader->parsePart (useIO, boundary);
    setNestedMessage (msgHeader);
  }
  else
  {
    retVal = parseBody (useIO, postNested, boundary, mbox); //just a simple part remaining
    setPostBody (postNested);
  }
  return retVal;
}

int
mimeHeader::parseBody (mimeIO & useIO, TQCString & messageBody,
                       const TQString& boundary, bool mbox)
{
  TQCString inputStr;
  TQCString buffer;
  TQString partBoundary;
  TQString partEnd;
  int retVal = 0;               //default is last part

  if (!boundary.isEmpty ())
  {
    partBoundary = TQString ("--") + boundary;
    partEnd = TQString ("--") + boundary + "--";
  }

  while (useIO.inputLine (inputStr))
  {
    //check for the end of all parts
    if (!partEnd.isEmpty ()
        && !tqstrnicmp (inputStr, partEnd.latin1 (), partEnd.length () - 1))
    {
      retVal = 0;               //end of these parts
      break;
    }
    else if (!partBoundary.isEmpty ()
             && !tqstrnicmp (inputStr, partBoundary.latin1 (),
                            partBoundary.length () - 1))
    {
      retVal = 1;               //continue with next part
      break;
    }
    else if (mbox && inputStr.find ("From ") == 0)
    {
      retVal = 0;               // end of mbox
      break;
    }
    buffer += inputStr;
    if (buffer.length () > 16384)
    {
      messageBody += buffer;
      buffer = "";
    }
  }

  messageBody += buffer;
  return retVal;
}

bool
mimeHeader::parseHeader (mimeIO & useIO)
{
  bool mbox = false;
  bool first = true;
  mimeHdrLine my_line;
  TQCString inputStr;

  kdDebug(7116) << "mimeHeader::parseHeader - starting parsing" << endl;
  while (useIO.inputLine (inputStr))
  {
    int appended;
    if (inputStr.find ("From ") != 0 || !first)
    {
      first = false;
      appended = my_line.appendStr (inputStr);
      if (!appended)
      {
        addHdrLine (&my_line);
        appended = my_line.setStr (inputStr);
      }
      if (appended <= 0)
        break;
    }
    else
    {
      mbox = true;
      first = false;
    }
    inputStr = (const char *) NULL;
  }

  kdDebug(7116) << "mimeHeader::parseHeader - finished parsing" << endl;
  return mbox;
}

mimeHeader *
mimeHeader::bodyPart (const TQString & _str)
{
  // see if it is nested a little deeper
  int pt = _str.find('.');
  if (pt != -1)
  {
    TQString tempStr = _str;
    mimeHeader *tempPart;

    tempStr = _str.right (_str.length () - pt - 1);
    if (nestedMessage)
    {
      kdDebug(7116) << "mimeHeader::bodyPart - recursing message" << endl;
      tempPart = nestedMessage->nestedParts.at (_str.left(pt).toULong() - 1);
    }
    else
    {
      kdDebug(7116) << "mimeHeader::bodyPart - recursing mixed" << endl;
      tempPart = nestedParts.at (_str.left(pt).toULong() - 1);
    }
    if (tempPart)
      tempPart = tempPart->bodyPart (tempStr);
    return tempPart;
  }

  kdDebug(7116) << "mimeHeader::bodyPart - returning part " << _str << endl;
  // or pick just the plain part
  if (nestedMessage)
  {
    kdDebug(7116) << "mimeHeader::bodyPart - message" << endl;
    return nestedMessage->nestedParts.at (_str.toULong () - 1);
  }
  kdDebug(7116) << "mimeHeader::bodyPart - mixed" << endl;
  return nestedParts.at (_str.toULong () - 1);
}

void mimeHeader::serialize(TQDataStream& stream)
{
  int nestedcount = nestedParts.count();
  if (nestedParts.isEmpty() && nestedMessage)
    nestedcount = 1;
  stream << nestedcount << contentType << TQString (getTypeParm ("name")) << _contentDescription
    << _contentDisposition << contentEncoding << contentLength << partSpecifier;
  // serialize nested message
  if (nestedMessage)
    nestedMessage->serialize(stream);

  // serialize nested parts
  if (!nestedParts.isEmpty())
  {
    TQPtrListIterator < mimeHeader > it(nestedParts);
    mimeHeader* part;
    while ( (part = it.current()) != 0 )
    {
      ++it;
      part->serialize(stream);
    }
  }
}

#ifdef KMAIL_COMPATIBLE
// compatibility subroutines
TQString
mimeHeader::bodyDecoded ()
{
  kdDebug(7116) << "mimeHeader::bodyDecoded" << endl;
  TQByteArray temp;

  temp = bodyDecodedBinary ();
  return TQString::fromLatin1 (temp.data (), temp.count ());
}

TQByteArray
mimeHeader::bodyDecodedBinary ()
{
  TQByteArray retVal;

  if (contentEncoding.find ("quoted-printable", 0, false) == 0)
    retVal = KCodecs::quotedPrintableDecode(postMultipartBody);
  else if (contentEncoding.find ("base64", 0, false) == 0)
    KCodecs::base64Decode(postMultipartBody, retVal);
  else retVal = postMultipartBody;

  kdDebug(7116) << "mimeHeader::bodyDecodedBinary - size is " << retVal.size () << endl;
  return retVal;
}

void
mimeHeader::setBodyEncodedBinary (const TQByteArray & _arr)
{
  setBodyEncoded (_arr);
}

void
mimeHeader::setBodyEncoded (const TQByteArray & _arr)
{
  TQByteArray setVal;

  kdDebug(7116) << "mimeHeader::setBodyEncoded - in size " << _arr.size () << endl;
  if (contentEncoding.find ("quoted-printable", 0, false) == 0)
    setVal = KCodecs::quotedPrintableEncode(_arr);
  else if (contentEncoding.find ("base64", 0, false) == 0)
    KCodecs::base64Encode(_arr, setVal);
  else
    setVal.duplicate (_arr);
  kdDebug(7116) << "mimeHeader::setBodyEncoded - out size " << setVal.size () << endl;

  postMultipartBody.duplicate (setVal);
  kdDebug(7116) << "mimeHeader::setBodyEncoded - out size " << postMultipartBody.size () << endl;
}

TQString
mimeHeader::iconName ()
{
  TQString fileName;

  // FIXME: bug?  Why throw away this data?
  fileName =
    KMimeType::mimeType (contentType.lower ())->icon (TQString(), false);
  fileName =
    TDEGlobal::instance ()->iconLoader ()->iconPath (fileName, TDEIcon::Desktop);
//  if (fileName.isEmpty())
//    fileName = TDEGlobal::instance()->iconLoader()->iconPath( "unknown", TDEIcon::Desktop );
  return fileName;
}

void
mimeHeader::setNestedMessage (mailHeader * inPart, bool destroy)
{
//  if(nestedMessage && destroy) delete nestedMessage;
  nestedMessage = inPart;
}

TQString
mimeHeader::headerAsString ()
{
  mimeIOTQString myIO;

  outputHeader (myIO);
  return myIO.getString ();
}

TQString
mimeHeader::magicSetType (bool aAutoDecode)
{
  TQString mimetype;
  TQByteArray body;
  KMimeMagicResult *result;

  KMimeMagic::self ()->setFollowLinks (TRUE); // is it necessary ?

  if (aAutoDecode)
    body = bodyDecodedBinary ();
  else
    body = postMultipartBody;

  result = KMimeMagic::self ()->findBufferType (body);
  mimetype = result->mimeType ();
  contentType = mimetype;
  return mimetype;
}
#endif
