/*************************************************************************** showrecord.cpp - description ------------------- begin : Thu Dec 28 2000 copyright : (C) 2000-2001 by Eggert Ehmke email : eggert.ehmke@berlin.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 "showrecordelem.h" int const ShowRecordElem::continueShowHeaders( 0 ); int const ShowRecordElem::cancelShowHeaders( 1 ); ShowRecordElem::ShowRecordElem () { //set default values m_from = "???"; m_subject = "???"; m_size = 0; m_pItem = NULL; m_new = false; markAtViewRefresh = false; } ShowRecordElem::ShowRecordElem( int number, TQString& uid, bool isNew ) { //set default values m_from = "???"; m_subject = "???"; m_size = 0; m_pItem = NULL; markAtViewRefresh = false; //set given values m_nNumber = number; m_uid = uid; m_new = isNew; } TQCString ShowRecordElem::scanHeader( const TQString& item ) const { TQCString headerline( "" ); //found header line //get e.g. the "From:" line, starting with cr,lf,"From:" and ending //with a carriage return //build the search string TQString searchstring( TQString( "\r\n%1:" ).arg( item ) ); //searching... int pos1 = m_header.find( searchstring, 0, FALSE ); int pos2 = m_header.find( '\r', pos1 + 2 ); //cut out the interesting content, if we have found a matching line //if we have found nothing, the returned string will be "" if( pos1 >= 0 ) { headerline = m_header.mid( pos1 + searchstring.length(), pos2 - pos1 - searchstring.length() ); } return headerline; } void ShowRecordElem::setHeader( const TQString& header ) { //store given header m_header = header.ascii(); //extract sender and store it TQCString from = scanHeader( "From" ); from = from.simplifyWhiteSpace(); setFrom( from ); //extract addressee and store it TQCString to = scanHeader( "To" ); to = to.simplifyWhiteSpace(); setTo (to); //extract subject and store it TQCString subject = scanHeader( "Subject" ); subject = subject.simplifyWhiteSpace(); setSubject( subject ); //extract date and store it TQCString date = scanHeader( "Date" ); setDate( date ); //extract content type TQCString content = scanHeader( "Content-Type" ); content = content.simplifyWhiteSpace (); //remove the stuff after the content type; see RFC 2045 int posSemicolon = content.find( ';' ); if( posSemicolon != -1 ) { content.remove( posSemicolon, content.length() - posSemicolon ); } //store content type setContent (content); } void ShowRecordElem::setDate( const TQCString& date ) { DwDateTime dwDate; //this class represents an RFC-822 date-time; //see mimelib/datetime.h //convert and store the date-time dwDate.FromString( date ); dwDate.Parse(); m_unixDate.setTime_t( dwDate.AsUnixTime() ); } TQString ShowRecordElem::from() const { return Codecs::decodeRFC2047( m_from ); } TQString ShowRecordElem::to() const { return Codecs::decodeRFC2047( m_to ); } TQString ShowRecordElem::subject() const { return Codecs::decodeRFC2047( m_subject ); } TQString ShowRecordElem::date() const { return TDEGlobal::locale()->formatDateTime( m_unixDate, true, true ); } TQString ShowRecordElem::strUnixTime() const { return m_unixDate.toString( TQt::ISODate ); } TQString ShowRecordElem::strSize() const { return TQString( "%1" ).arg( m_size, 8 ); } TQString ShowRecordElem::state() const { if( m_new ) return i18n( "new" ); else return i18n( "old" ); } void ShowRecordElem::saveOptions( TQDomDocument& doc, TQDomElement& parent ) { //build item tag of this mail( with mail number) TQString hdr = TQString( ITEM_MESSAGE ); hdr.append( "%1" ); hdr = hdr.arg( m_nNumber ); //create a new element and store the mail meta data in it TQDomElement elem = doc.createElement( hdr ); elem.setAttribute( ATTRIBUTE_MAIL_NUMBER, m_nNumber ); elem.setAttribute( ATTRIBUTE_MAIL_SIZE, m_size ); elem.setAttribute( ATTRIBUTE_MAIL_UID, m_uid ); //create a sub element for the mail header in store the header in it TQDomElement subelem = doc.createElement( ITEM_MAIL_HEADER ); subelem.appendChild( doc.createTextNode( m_header ) ); //add header element to the mail element elem.appendChild( subelem ); //add mail element to the account (parent) element parent.appendChild( elem ); } void ShowRecordElem::readOptions( TQDomElement& elem ) { //get number, size and uid setNumber( elem.attribute( ATTRIBUTE_MAIL_NUMBER ).toInt() ); setSize( elem.attribute( ATTRIBUTE_MAIL_SIZE ).toInt() ); setUIDL( elem.attribute( ATTRIBUTE_MAIL_UID ) ); //search for the header item and read it TQDomElement subelem = elem.namedItem( ITEM_MAIL_HEADER ).toElement(); setHeader( subelem.text() ); //the mail is not new setNew( false ); } void ShowRecordElem::setFrom( const TQCString & from ) { m_from = from; } void ShowRecordElem::setTo( const TQCString & to ) { m_to = to; } void ShowRecordElem::setSubject( const TQCString & subject ) { m_subject = subject; } void ShowRecordElem::setContent( const TQCString& content ) { m_content = content; } TQString ShowRecordElem::header( ) const { return TQString( m_header ); } void ShowRecordElem::setUIDL( const TQString & uid ) { m_uid = uid; } TQString ShowRecordElem::uidl( ) const { return m_uid; } void ShowRecordElem::setSize( int size ) { m_size = size; } int ShowRecordElem::size( ) const { return m_size; } void ShowRecordElem::setNew( bool isnew ) { m_new = isnew; } bool ShowRecordElem::isNew( ) const { return m_new; } void ShowRecordElem::setNumber( int n ) { m_nNumber = n; } int ShowRecordElem::number( ) const { return m_nNumber; } TQString ShowRecordElem::content( ) const { return m_content; } void ShowRecordElem::setViewItem( ShowListViewItem* item ) { m_pItem = item; //marks the new entry if recommend by the filter if( markAtViewRefresh ) { //mark entry item->setSelected( true ); //delete flag markAtViewRefresh = false; } } ShowListViewItem * ShowRecordElem::viewItem( ) const { return m_pItem; } bool ShowRecordElem::isSelected( ) const { if( m_pItem != NULL ) return m_pItem->isSelected(); else return false; } TQString ShowRecordElem::strSizePrefix( ) const { TQString size; if( m_size >= 1024 * 1024 ) { //prefix is mega size = TQString( "%L1M" ).arg( ( (double)m_size / ( 1024 * 1024 ) ), 0, 'f', 1 ); } else if( m_size >= 1024 ) { //prefix is kilo size = TQString( "%L1K" ).arg( ( (double)m_size / 1024 ), 0, 'f', 1 ); } else //no prefix size = TQString( "%L1" ).arg( m_size ); return size; } TQString ShowRecordElem::decodeMailBody( TQByteArray body, bool preferHTML ) const { TQString charset; //charset of the content TQString encoding; //content transfer encoding //cast given body to a TQCString //class TQCString needs a null terminated char array to create //an object. Therefore we append an null byte to the given mail body body.resize( body.size() + 1 ); body[ body.size() - 1 ] = '\0'; TQCString strBody( (char *)body.data() ); //normalize line ends; remove all \r characters for( uint i = 0; i < strBody.size(); i++ ) if( strBody[ i ] == '\r' ) strBody.remove( i, 1 ); //get boundary that is separating the parts of a multipart message //if the header doesn't contain a boundary attribute, this messsage //has just one part TQString boundary = getBoundary(); //process body subject to it is a multipart messsage or not if( boundary == "" ) { //the message has just one body part //get the position of the first blank line int posBlankLine = strBody.find( "\n\n" ); //truncate body; the found blank line is separating the //header from the message strBody = strBody.mid( posBlankLine + 2 ); if( !strBody.isEmpty() ) //fixed bug 1773636 while( strBody[ 0 ] == '\n') strBody.remove( 0, 1 ); //get charset of the message; it is behind the //content type attribute in the header charset = getCharset(); //get transfer encoding type from the header encoding = getTransferEncoding(); } else { //the message has multiple parts //get positions of a plain text and html flag (value of the content type attribute) int posPlainFlag = strBody.find( "text/plain", 0, false ); int posHTMLFlag = strBody.find( "text/html", 0, false ); //just decode the body, if a plain text or a HTML part is available if( posPlainFlag != -1 || posHTMLFlag != -1 ) { //do we want to take the HTML part? bool hasHTML = posHTMLFlag != -1; bool takeHTML = ( hasHTML && preferHTML ) || posPlainFlag == -1; //now we want to extract the designated part //While the (truncated) mail text (or the header at the first pass) //contains a boundary attribute we will extract the designated part //between the boundaries int posInside; //a position inside the designated part while( boundary != "" ) { //get a position inside the designated part if( takeHTML ) posInside = strBody.find( "text/html", 0, false ); else posInside = strBody.find( "text/plain", 0, false ); //get length of the boundary int lengthBoundary = boundary.length(); //calculate the begin and end of the part to extract int beginPart = strBody.findRev( boundary.ascii(), posInside ) + lengthBoundary + 1; int lengthPart = strBody.findRev( '\n', strBody.find( boundary.ascii(), posInside ) ) - beginPart; strBody = strBody.mid( beginPart, lengthPart ); //looking for a further boundary attribute //get the position of the first occurance of "boundary=" int posBoundary = strBody.find( "boundary=", 0, false ); if( posBoundary >= 0 ) { //calculate positon of the first quote int posFirstQuote = posBoundary + 9; //get the position of closing quote int posSecondQuote = strBody.find( '"', posFirstQuote + 1 ); //get boundary string boundary.append( strBody.mid( posFirstQuote + 1, posSecondQuote - posFirstQuote - 1 ) ); } else boundary = ""; } //now we get charset and transfer encoding if available in the extracted //part //get the position of the first occurance of "charset=" int posCharset = strBody.find( "charset=", 0, false ); //continue, if a charset attribute was found if( posCharset >= 0 ) { //calculate positon of the value int posBeginValue = posCharset + 8; //get end of the value int posEndValue = strBody.find( '\n', posBeginValue ) - 1; //get charset charset.append( strBody.mid( posBeginValue, posEndValue - posBeginValue + 1 ) ); //remove quotes charset.remove( '"' ); //remove all content after the first semicolon (inclusive) int posSemicolon = charset.find( ';' ); charset = charset.left( posSemicolon ); } //get the position of the first occurance of "charset=" int posEncoding = strBody.find( "Content-Transfer-Encoding:", 0, false ); //continue, if a charset attribute was found if( posEncoding >= 0 ) { //calculate positon of the value int posBeginValue = posEncoding + 26; //get end of the value int posEndValue = strBody.find( '\n', posBeginValue ) - 1; //get charset encoding.append( strBody.mid( posBeginValue, posEndValue - posBeginValue + 1 ) ); //remove quotes and spaces encoding = encoding.stripWhiteSpace(); encoding.remove( '"' ); } //cut off the part header; the found blank line is separating the //part header from the message if( posCharset != -1 || posEncoding != -1 ) { int posBlankLine = strBody.find( "\n\n" ); strBody = strBody.mid( posBlankLine + 2 ); if( !strBody.isEmpty() ) //fixed bug 1773636 while( strBody[ 0 ] == '\n') strBody.remove( 0, 1 ); } } } //Good things come to those who wait. We have extract the message. //Now we have to decode the message, if it is encoded if( encoding == "quoted-printable" && !strBody.isEmpty() ) //fixed bug 1773636 { strBody = KCodecs::quotedPrintableDecode( strBody ); } return TQString( strBody ); } TQString ShowRecordElem::getBoundary( ) const { TQString boundary; //check, whether it is a multipart message if( m_content.contains( "multipart", false ) ) { //it is a multipart message //get the position of the first occurance of "boundary=" int posBoundary = m_header.find( "boundary=", 0, false ); //continue, if a boundary attribute was found if( posBoundary >= 0 ) { //calculate positon of the first quote int posFirstQuote = posBoundary + 9; //get the position of closing quote int posSecondQuote = m_header.find( '"', posFirstQuote + 1 ); //get boundary string boundary.append( m_header.mid( posFirstQuote + 1, posSecondQuote - posFirstQuote - 1 ) ); } } return boundary; } TQString ShowRecordElem::getCharset( ) const { TQString charset; //get the position of the first occurance of "charset=" int posCharset = m_header.find( "charset=", 0, false ); //continue, if a charset attribute was found if( posCharset >= 0 ) { //calculate positon of the value int posBeginValue = posCharset + 8; //get end of the value int posEndValue = m_header.find( '\r', posBeginValue ) - 1; //get charset charset.append( m_header.mid( posBeginValue, posEndValue - posBeginValue + 1 ) ); //remove quotes charset.remove( '"' ); //remove all content after the first semicolon (inclusive) int posSemicolon = charset.find( ';' ); charset = charset.left( posSemicolon ); } return TQString( charset ); } TQString ShowRecordElem::getTransferEncoding( ) const { TQString encoding; //get the position of the first occurance of "charset=" int posEncoding = m_header.find( "Content-Transfer-Encoding:", 0, false ); //continue, if a charset attribute was found if( posEncoding >= 0 ) { //calculate positon of the value int posBeginValue = posEncoding + 26; //get end of the value int posEndValue = m_header.find( '\r', posBeginValue ) - 1; //get charset encoding.append( m_header.mid( posBeginValue, posEndValue - posBeginValue + 1 ) ); //remove quotes and spaces encoding = encoding.stripWhiteSpace(); encoding.remove( '"' ); } return TQString( encoding ); } int ShowRecordElem::showHeader( TQString& account ) { //show header TQString tsubject = subject(); TQString tmailheader = header(); //create and open the window ShowHeaderDialog dlg( kapp->mainWidget(), account, tsubject, tmailheader ); int ret = dlg.exec(); //returns the matching value return ret == TQDialog::Accepted ? ShowRecordElem::continueShowHeaders : ShowRecordElem::cancelShowHeaders; } FilterAction_Type ShowRecordElem::applyHeaderFilter( HeaderFilter* filter, TQString account, TQString& mailbox, FilterLog* log ) { FilterAction_Type action = filter->check( from(), to(), size(), subject(), header(), account, mailbox ); //if the action is MARK, the related view entry shall be marked at the next view refresh if( action == FActMark ) markAtViewRefresh = true; //if the action is DELETE, we add a entry to the log if( log == NULL ) kdError( "ShowRecordElem::applyHeaderFilter: Pointer to the filter log is NULL. Can't write to log." ); if( action == FActDelete && log != NULL ) log->addDeletedMail( sentDateTime(), from(), account, subject() ); if( action == FActMove && log != NULL ) log->addMovedMail( sentDateTime(), from(), account, subject(), mailbox ); return action; } TQDateTime ShowRecordElem::sentDateTime() const { return m_unixDate; } void ShowRecordElem::writeToMoveLog( FilterLog * log, TQString account, TQString mailbox ) { log->addMovedMail( sentDateTime(), from(), account, subject(), mailbox ); } void ShowRecordElem::writeToDeleteLog( FilterLog * log, TQString account ) { log->addDeletedMail( sentDateTime(), from(), account, subject() ); } void ShowRecordElem::setMarkAtNextViewRefresh( ) { markAtViewRefresh = true; }