/***************************************************************************
                          mimemessage.cpp  -  description
                             -------------------
    begin                : Sat Mar 8 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mkb137b@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/*
   The decode...() functions and getCodecByName() come from KMail
   and are therefore (C) KMail developers.
*/

#include "mimemessage.h"

#include <qregexp.h>
#include <qtextcodec.h>

#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <kcharsets.h>
#include <kmdcodec.h>

#include <stdlib.h>

#include "../kmessdebug.h"


// The constructor
MimeMessage::MimeMessage()
 : QObject(0, "MimeMessage")
{
#ifdef KMESSDEBUG_MIMEMESSAGE
  kdDebug() << "MimeMessage - Constructor." << endl;
#endif
}



// The constructor that parses a message
MimeMessage::MimeMessage(const QString &message)
 : QObject(0, "MimeMessage")
{
#ifdef KMESSDEBUG_MIMEMESSAGE
  kdDebug() << "MimeMessage - Constructor." << endl;
#endif
  parseMessage( message );
}


MimeMessage::MimeMessage(const QByteArray &message)
  : QObject(0, "MimeMessage")
{
#ifdef KMESSDEBUG_MIMEMESSAGE
  kdDebug() << "MimeMessage - Constructor." << endl;
#endif

  // we get the header without the binary data that comes after
  int nullPos = message.find('\0');
  QString messageHeader = QString::fromUtf8(message.data(), (nullPos == -1) ? message.size() : nullPos );

  QRegExp rx("Content-Type: ([A-Za-z0-9$!*/\\-]*)");
  rx.search( messageHeader );
  QString type = rx.cap(1);

  // if the type is p2p, we must take care of the binary data that comes after the header
  if( type == "application/x-msnmsgrp2p"  )
  {
#ifdef KMESSDEBUG_MIMEMESSAGE
    kdDebug() << "MimeMessage - The message is of type P2P" << endl;
#endif

    QString msg(message);
    uint endMime   = msg.find("\r\n\r\n");
    uint startBin  = endMime + 4; // 2 newlines
    uint binLength = message.size() - startBin;

    // Extract the find Mime fields from the message (+1 base)
    parseMessage( QString::fromUtf8( message.data(), startBin ) );

    // Extract the binary data as well
    binaryData_.duplicate(message.data() + startBin, binLength);

#ifdef KMESSTEST
    ASSERT(binaryData_.size() == binLength);
#endif

#ifdef KMESSDEBUG_MIMEMESSAGE
    QString hexHeader = "";
    uint headerEnd = (binLength < 48 ? binLength : 48);

    const char hexMap[] = "0123456789abcdef";

    for (uint i = 0; i < headerEnd; i++)
    {
      int upper = (binaryData_[i] & 0xf0) >> 4;
      int lower = (binaryData_[i] & 0x0f);

      hexHeader += hexMap[upper];
      hexHeader += hexMap[lower];
      hexHeader += " ";
    }

    kdDebug() << "MimeMessage: P2P header is: " << hexHeader << endl;
#endif
  }
  else
  {
    // Added fromUtf8 call to translate Unicode characters (like Chinese)
    parseMessage( QString::fromUtf8( message.data(), message.size()) );
  }
}

// The copy constructor
MimeMessage::MimeMessage(const MimeMessage& other)
 : QObject()
{
#ifdef KMESSDEBUG_MIMEMESSAGE
  kdDebug() << "MimeMessage: copy constructor" << endl;
#endif

  // Get the body of the other message
  fields_     = other.fields_;
  values_     = other.values_;
  body_       = other.body_;
  binaryData_ = other.binaryData_;

  // QValueList is implicitly shared, so the assign operation
  // does not create another copy. It uses "copy on write".

  // QByteArray is explicitly shared, but it's not a problem here,
  // since we don't modify the array data of binaryData_.

#ifdef KMESSTEST
  // Make sure the ascii-zero characters are copied correctly:
  ASSERT( binaryData_.size() == other.binaryData_.size() );
  ASSERT( fields_.size()     == other.fields_.size() );
#endif
}



// The destructor
MimeMessage::~MimeMessage()
{
}



// Add a field to the message
void MimeMessage::addField(const QString& field, const QString& value)
{
  fields_ << field;
  values_ << value;
}


// Change a field, or add it
void MimeMessage::setField(const QString& field, const QString& value)
{
  int index= fields_.findIndex(field);
  if(-1 == index)
  {
    addField(field, value);
  }
  else
  {
    values_[index]= value;
  }
}



// decodes MIME strings like =?iso...=...?= ...
QString MimeMessage::decodeRFC2047String(const QCString& aStr) const
{
  QString result;
  QCString charset;
  char *pos, *beg, *end, *mid=0;
  QCString str, cstr, LWSP_buffer;
  char encoding='Q', ch;
  bool valid, lastWasEncodedWord=FALSE;
  const int maxLen=200;
  int i;

  if (aStr.find("=?") < 0)
  {
    //QString str = messageCodec->toUnicode(aStr);
    QString str = QString::fromUtf8( aStr );
    if (str.find('\n') == -1) return str;
    QString str2((QChar*)0, str.length());
    uint i = 0;
    while (i < str.length())
    {
      if (str[i] == '\n')
      {
        str2 += ' ';
        i += 2;
      } else {
        str2 += str[i];
        i++;
      }
    }
    return str2;
  }

  for (pos=aStr.data(); *pos; pos++)
  {
    // line unfolding
    if ( pos[0] == '\r' && pos[1] == '\n' ) {
      pos++;
      continue;
    }
    if ( pos[0] == '\n' )
      continue;
    // collect LWSP after encoded-words,
    // because we might need to throw it out
    // (when the next word is an encoded-word)
    if ( lastWasEncodedWord && ( pos[0] == ' ' || pos[0] == '\t' ) ) {
      LWSP_buffer += pos[0];
      continue;
    }
    // verbatimly copy normal text
    if (pos[0]!='=' || pos[1]!='?')
    {
      result += LWSP_buffer + pos[0];
      LWSP_buffer = 0;
      lastWasEncodedWord = FALSE;
      continue;
    }
    // found possible encoded-word
    beg = pos+2;
    end = beg;
    valid = TRUE;
    // parse charset name
    charset = "";
    for (i=2,pos+=2; i<maxLen && (*pos!='?'&&(*pos==' '||ispunct(*pos)||isalnum(*pos))); i++)
    {
      charset += *pos;
      pos++;
    }
    if (*pos!='?' || i<4 || i>=maxLen) valid = FALSE;
    else
    {
      // get encoding and check delimiting question marks
      encoding = toupper(pos[1]);
      if (pos[2]!='?' || (encoding!='Q' && encoding!='B'))
        valid = FALSE;
      pos+=3;
      i+=3;
    }
    if (valid)
    {
      mid = pos;
      // search for end of encoded part
      while (i<maxLen && *pos && !(*pos=='?' && *(pos+1)=='='))
      {
        i++;
        pos++;
      }
      end = pos+2;//end now points to the first char after the encoded string
      if (i>=maxLen || !*pos) valid = FALSE;
    }
    if (valid)
    {
      // valid encoding: decode and throw away separating LWSP
      ch = *pos;
      *pos = '\0';
      str = QCString(mid).left((int)(mid - pos - 1));
      if (encoding == 'Q')
      {
        // decode quoted printable text
        for (i=str.length()-1; i>=0; i--)
          if (str[i]=='_') str[i]=' ';
        cstr = KCodecs::quotedPrintableDecode(str);
      }
      else
      {
        // decode base64 text
        cstr = KCodecs::base64Decode(str);
      }
      QTextCodec *codec = getCodecByName(charset);
      if (!codec)
      {
        result += QString::fromUtf8( cstr );
      }
      else
      {
        result += codec->toUnicode(cstr);
      }

      lastWasEncodedWord = TRUE;

      *pos = ch;
      pos = end -1;
    }
    else
    {
      // invalid encoding, keep separating LWSP.
      //result += "=?";
      //pos = beg -1; // because pos gets increased shortly afterwards
      pos = beg - 2;
      result += LWSP_buffer;
      result += *pos++;
      result += *pos;
      lastWasEncodedWord = FALSE;
    }
    LWSP_buffer = 0;
  }
  return result;
}



// Return the body of the message
const QString& MimeMessage::getBody() const
{
  return body_;
}

// Return the P2P data of the message
const QByteArray& MimeMessage::getBinaryData() const
{
  return binaryData_;
}

// Finds a QTextCodec by name
QTextCodec* MimeMessage::getCodecByName(const QCString& codecName )
{
  if ( codecName.isEmpty() )
  {
    return 0;
  }

  return KGlobal::charsets()->codecForName( codecName.lower() );
}



// Return the field and value at the given index
void MimeMessage::getFieldAndValue(QString& field, QString& value, const uint& index) const
{
  if ( index < fields_.count() )
  {
    field = fields_[index];
    value = values_[index];
  }
  else
  {
    field = QString::null;
    value = QString::null;
  }
}



// Return the message fields as a big string
QString MimeMessage::getFields() const
{
  QString message = "";

  // Get the fields and values
  for ( uint i = 0; i < fields_.count(); i++ )
  {
    message += fields_[i] + ": " + values_[i] + "\r\n";
  }

  return message;
}



// Return the entire message as a big string
QByteArray MimeMessage::getMessage() const
{
  // Combine the message parts, convert to UTF8 string
  QCString textPart = QString(getFields() + "\r\n" + body_).utf8();

  if(binaryData_.isEmpty())
  {
    // Downgrade to QByteArray.
    // If this is not done manually, it happens anyway, but with a '\0' padded.
    QByteArray textPartBin;
    textPartBin.duplicate(textPart.data(), textPart.length());
    return textPartBin;
  }
  else
  {
    // Concatenate the message and binary data
    QByteArray p2pMessage;
    uint  totalLength  = textPart.length() + binaryData_.size();
    char *rawData      = new char[ totalLength ];
    uint  offset       = 0;

    // Copy the parts to the array
    memcpy(rawData + offset, textPart.data(),    textPart.length());  offset += textPart.length();
    memcpy(rawData + offset, binaryData_.data(), binaryData_.size()); offset += binaryData_.size();

#ifdef KMESSTEST
    ASSERT( offset == totalLength );
#endif

    // Load the rawData in the QByteArray, and return it
    p2pMessage.assign( rawData, totalLength );  // does the deletion automatically
    return p2pMessage;
  }
}



// The total number of fields
uint MimeMessage::getNoFields() const
{
  return fields_.count();
}



// Get one parameter of a value that has multiple parameters
QString MimeMessage::getSubValue(const QString& field, const QString& subField) const
{
  QString value, parameter;
  int     left, right;
  // Get the value referred to by the field
  value = getValue( field );
  if ( !value.isNull() )
  {
    // If the subfield isn't specified, then get whatever is at the start of the message until the
    //  first semicolon or the end of the line
    if ( subField.isNull() )
    {
      left = 0;
    }
    else
    {
      // The left of the parameter is "subField=", the right is the next semicolon or the end of the line
      left = value.find( subField + "=" );
      if ( left >= 0 )
      {
        left += subField.length() + 1;
      }
    }
    if ( left >= 0 )
    {
      right = value.find( ";", left );
      if ( right < 0 )
      {
        right = value.length();
      }
      // Get the parameter
      parameter = value.mid( left, ( right - left ) );
      return parameter;
    }
  }
  return QString::null;
}



// Get a value given a field
const QString& MimeMessage::getValue(const QString& field ) const
{
  // Search the fields for a match
  for ( uint i = 0; i < fields_.count(); i++ )
  {
    if ( fields_[i] == field )
    {
      return values_[i];
    }
  }
  kdDebug() << "MimeMessage - WARNING - This message contained no field \"" << field << "\"." << endl;
  return QString::null;
}



// Test whether a given field exists in the message header
bool MimeMessage::hasField(const QString& field) const
{
  // Search the fields for a match
  for ( uint i = 0; i < fields_.count(); i++ )
  {
    if ( fields_[i] == field )
    {
      return true;
    }
  }

  return false;
}


// Parse the message into type, body, and fields and values
void MimeMessage::parseMessage(const QString& message)
{
#ifdef KMESSDEBUG_MIMEMESSAGE
  kdDebug() << "MimeMessage - Parse the message." << endl;
#endif
  QString      head, field, value;
  QStringList  lines;
  // Split the message into head and body
  splitMessage( head, body_, message );
  // Split the head into its various lines
  splitHead( lines, head );
  // Split all the lines into field and value
  for ( uint i = 0; i < lines.count(); i++ )
  {
    splitLine( field, value, lines[i] );
    if ( !field.isEmpty() )
    {
      // Add the fields and values to the lists
      addField( field, value );
    }
  }
#ifdef KMESSTEST
  ASSERT( fields_.count() == values_.count() );
#endif
}



// Print the contents of the message to kdDebug (for debugging purposes)
void MimeMessage::print() const
{
  kdDebug() << "MimeMessage - Printing MIME message, " << fields_.count() << " fields." << endl;
  // Get the fields and values
  for ( uint i = 0; i < fields_.count(); i++ )
  {
    kdDebug() << "MimeMessage - Print - Field: \"" << fields_[i] << "\" value: \"" << values_[i] << "\"" << endl;
  }
  kdDebug() << "MimeMessage - Print - Body:" << endl << body_ << endl;
}



// Set the message body
void MimeMessage::setBody(const QString& body)
{
  body_ = body;
}



// Set the P2P data of the message
void MimeMessage::setBinaryData(const char header[48], const char* body, const char footer[4], uint bodylength)
{
  // Concatenate the message.
  QByteArray newBinaryData;
  uint  totalLength  = 48 + bodylength + 4;
  char *rawData      = new char[ totalLength ];
  uint  offset       = 0;

  // Copy the parts to the array
  memcpy(rawData + offset, header,  48);          offset += 48;
  memcpy(rawData + offset, body,    bodylength);  offset += bodylength;
  memcpy(rawData + offset, footer,  4);           offset += 4;

#ifdef KMESSTEST
  ASSERT( offset == totalLength );
#endif

  // Does auto-deletion automatically
  binaryData_.assign(rawData, totalLength);
}



// Split a line between field and value
void MimeMessage::splitLine(QString& field, QString& value, const QString& line) const
{
  int index;
  index = line.find(":");
  if ( index >= 0 )
  {
    field = line.left( index );
    if ( ( index + 1 ) < (int)line.length() )
    {
      value = line.right( line.length() - index - 2 );
    }
    else
    {
      value = QString::null;
    }
  }
  else
  {
    kdDebug() << "MimeMessage: WARNING - Couldn't split line '" << line << "'" << endl;
  }
}



// Split the message head into components and store it in the string list
void MimeMessage::splitHead(QStringList& stringList, const QString& head) const
{
  int left = 0, right = 0;
  QString line;

  stringList.clear();
  right = head.find( "\r\n" );
  while ( right >= 0 )
  {
    // Get the message between "left" and "right".
    line = head.mid( left, right - left );
    stringList << line;

    // Find the next occurence of "\r\n" after left.
    left = right + 2;
    right = head.find( "\r\n", left );

    if(right == -1 && ! head.endsWith("\r\n"))
    {
      // This happens with MSNP2P/SLP content messages
      // I try to parse with the MimeMessage constructor.
      line = head.mid(left);
      stringList << line;
    }
  }
}



// Split a message into head and body
void MimeMessage::splitMessage(QString& head, QString& body, const QString& message) const
{
  int index;

  // The message is split into head and body at "\r\n\r\n"
  index = message.find( "\r\n\r\n" );
  if ( index < 0 )
  {
    head = message;
    body = "";
  }
  else
  {
    head = message.left( index + 2 ); // Keep a "\r\n" at the end
    body = message.right( message.length() - index - 4 );
  }
}

#include "mimemessage.moc"
