/***************************************************************************
 *   Copyright (C) 2008 by SK Communications.                              *
 *   http://kldp.net/projects/nateon/                                      *
 *                                                                         *
 *   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 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#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 "../knateondebug.h"

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


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


MimeMessage::MimeMessage(QByteArray message)
  : QObject()
{
#ifdef NETDEBUG
	kdDebug() << "MimeMessage - Constructor." << endl;
#endif

    // we get the header without the binary data that comes after
    uint 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 NETDEBUG
        kdDebug() << "MimeMessage - The message is of type P2P" << endl;
#endif

        QString msg(message);
        uint endMime   = msg.find("\r\n\r\n");
        // 2 newlines
        uint startBin  = endMime + 4;
        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 KNATEONTEST
        ASSERT(binaryData_.size() == binLength);
#endif

#ifdef KNATEONDEBUG_MIMEMESSAGE
        QString salida = "";
        QString num;
        char *rawMessageData = message.data();
        uint headerEnd = (binLength < 48 ? binLength : 48);

        for (uint i = 0; i < headerEnd; i++)
        {
            binaryData_[ i ] = rawMessageData[ startBin + i ];
            num = num.setNum( (unsigned char) binaryData_[i], 16 );
            salida.append( num );
            if(((i + 1) % 4) == 0) salida.append(' ');
        }
#ifdef NETDEBUG
        kdDebug() << "MimeMessage: P2P header is: " << salida << endl;
#endif
#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 NETDEBUG
    kdDebug() << "MimeMessage - Copy constructor." << endl;
#endif
    QString field, value;

    // Get the fields and values from the other message
    for ( uint i = 0; i < other.getNoFields(); i++ )
    {
        other.getFieldAndValue( field, value, i );
        fields_ << field;
        values_ << value;
    }
    // Get the body of the other message
    other.getBody( body_ );

    // Copy the P2P data
    binaryData_ = other.binaryData_;
#ifdef KNATEONTEST
    // Make sure the ascii-zero characters are copied correctly:
    ASSERT( binaryData_.size() == other.binaryData_.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
void MimeMessage::getBody(QString& body) const
{
    body = body_;
}


// 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
QString MimeMessage::getMessage() const
{
    QString message;

    message =  getFields();
    message += "\r\n";
    // Add the body
    message += body_;

    return message;
}


// 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];
        }
    }
#ifdef NETDEBUG
    kdDebug() << "MimeMessage - WARNING - This message contained no field \"" << field << "\"." << endl;
#endif
    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 NETDEBUG
    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 KNATEONTEST
    ASSERT( fields_.count() == values_.count() );
#endif
}


void MimeMessage::print() const
{
#ifdef NETDEBUG
    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;
#endif
}


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


/// 헤더의 필드들을 필드 값으로 구분하는 함수.
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 - 1 );
        }
        else
        {
            value = QString::null;
        }
    }
#ifdef NETDEBUG
    else
    {
        kdDebug() << "MimeMessage: WARNING - Couldn't split line '" << line << "'" << endl;
    }
#endif
}


// 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
    {
		// Keep a "\r\n" at the end
        head = message.left( index + 2 );
        body = message.right( message.length() - index - 4 );
    }
}
#include "mimemessage.moc"
