// kmmessage.cpp // if you do not want GUI elements in here then set ALLOW_GUI to 0. #include // needed temporarily until KMime is replacing the partNode helper class: #include "partNode.h" #define ALLOW_GUI 1 #include "kmkernel.h" #include "kmmessage.h" #include "mailinglist-magic.h" #include "messageproperty.h" using KMail::MessageProperty; #include "objecttreeparser.h" using KMail::ObjectTreeParser; #include "kmfolderindex.h" #include "undostack.h" #include "kmversion.h" #include "headerstrategy.h" #include "globalsettings.h" using KMail::HeaderStrategy; #include "kmaddrbook.h" #include "kcursorsaver.h" #include "templateparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using KMime::HeaderParsing::parseAddressList; using namespace KMime::Types; #include #include #include #include #include #include #include #include #include #include #include "util.h" #if ALLOW_GUI #include #endif using namespace KMime; static DwString emptyString(""); // Values that are set from the config file with KMMessage::readConfig() static TQString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr; static bool sSmartQuote, sWordWrap; static int sWrapCol; static TQStringList sPrefCharsets; TQString KMMessage::sForwardStr; const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich(); //helper static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ); TQValueList KMMessage::sPendingDeletes; //----------------------------------------------------------------------------- KMMessage::KMMessage(DwMessage* aMsg) : KMMsgBase() { init( aMsg ); // aMsg might need assembly mNeedsAssembly = true; } //----------------------------------------------------------------------------- KMMessage::KMMessage(KMFolder* parent): KMMsgBase(parent) { init(); } //----------------------------------------------------------------------------- KMMessage::KMMessage(KMMsgInfo& msgInfo): KMMsgBase() { init(); // now overwrite a few from the msgInfo mMsgSize = msgInfo.msgSize(); mFolderOffset = msgInfo.folderOffset(); mStatus = msgInfo.status(); mEncryptionState = msgInfo.encryptionState(); mSignatureState = msgInfo.signatureState(); mMDNSentState = msgInfo.mdnSentState(); mDate = msgInfo.date(); mFileName = msgInfo.fileName(); KMMsgBase::assign(&msgInfo); } //----------------------------------------------------------------------------- KMMessage::KMMessage(const KMMessage& other) : KMMsgBase( other ), ISubject(), mMsg(0) { init(); // to be safe assign( other ); } void KMMessage::init( DwMessage* aMsg ) { mNeedsAssembly = false; if ( aMsg ) { mMsg = aMsg; } else { mMsg = new DwMessage; } mOverrideCodec = 0; mDecodeHTML = false; mComplete = true; mReadyToShow = true; mMsgSize = 0; mMsgLength = 0; mFolderOffset = 0; mStatus = KMMsgStatusNew; mEncryptionState = KMMsgEncryptionStateUnknown; mSignatureState = KMMsgSignatureStateUnknown; mMDNSentState = KMMsgMDNStateUnknown; mDate = 0; mUnencryptedMsg = 0; mLastUpdated = 0; mCursorPos = 0; mMsgInfo = 0; mIsParsed = false; } void KMMessage::assign( const KMMessage& other ) { MessageProperty::forget( this ); delete mMsg; delete mUnencryptedMsg; mNeedsAssembly = true;//other.mNeedsAssembly; if( other.mMsg ) mMsg = new DwMessage( *(other.mMsg) ); else mMsg = 0; mOverrideCodec = other.mOverrideCodec; mDecodeHTML = other.mDecodeHTML; mMsgSize = other.mMsgSize; mMsgLength = other.mMsgLength; mFolderOffset = other.mFolderOffset; mStatus = other.mStatus; mEncryptionState = other.mEncryptionState; mSignatureState = other.mSignatureState; mMDNSentState = other.mMDNSentState; mIsParsed = other.mIsParsed; mDate = other.mDate; if( other.hasUnencryptedMsg() ) mUnencryptedMsg = new KMMessage( *other.unencryptedMsg() ); else mUnencryptedMsg = 0; setDrafts( other.drafts() ); setTemplates( other.templates() ); //mFileName = ""; // we might not want to copy the other messages filename (?) //KMMsgBase::assign( &other ); } //----------------------------------------------------------------------------- KMMessage::~KMMessage() { delete mMsgInfo; delete mMsg; kmkernel->undoStack()->msgDestroyed( this ); } //----------------------------------------------------------------------------- void KMMessage::setReferences(const TQCString& aStr) { if (aStr.isNull()) return; mMsg->Headers().References().FromString(aStr); mNeedsAssembly = true; } //----------------------------------------------------------------------------- TQCString KMMessage::id() const { DwHeaders& header = mMsg->Headers(); if (header.HasMessageId()) return KMail::Util::CString( header.MessageId().AsString() ); else return ""; } //----------------------------------------------------------------------------- //WARNING: This method updates the memory resident cache of serial numbers //WARNING: held in MessageProperty, but it does not update the persistent //WARNING: store of serial numbers on the file system that is managed by //WARNING: KMMsgDict void KMMessage::setMsgSerNum(unsigned long newMsgSerNum) { MessageProperty::setSerialCache( this, newMsgSerNum ); } //----------------------------------------------------------------------------- bool KMMessage::isMessage() const { return true; } //----------------------------------------------------------------------------- bool KMMessage::transferInProgress() const { return MessageProperty::transferInProgress( getMsgSerNum() ); } //----------------------------------------------------------------------------- void KMMessage::setTransferInProgress(bool value, bool force) { MessageProperty::setTransferInProgress( getMsgSerNum(), value, force ); if ( !transferInProgress() && sPendingDeletes.contains( this ) ) { sPendingDeletes.remove( this ); if ( parent() ) { int idx = parent()->find( this ); if ( idx > 0 ) { parent()->removeMsg( idx ); } } } } bool KMMessage::isUrgent() const { return headerField( "Priority" ).contains( "urgent", false ) || headerField( "X-Priority" ).startsWith( "2" ); } //----------------------------------------------------------------------------- void KMMessage::setUnencryptedMsg( KMMessage* unencrypted ) { delete mUnencryptedMsg; mUnencryptedMsg = unencrypted; } //----------------------------------------------------------------------------- //FIXME: move to libemailfunctions KPIM::EmailParseResult KMMessage::isValidEmailAddressList( const TQString& aStr, TQString& brokenAddress ) { if ( aStr.isEmpty() ) { return KPIM::AddressEmpty; } TQStringList list = KPIM::splitEmailAddrList( aStr ); for( TQStringList::const_iterator it = list.begin(); it != list.end(); ++it ) { KPIM::EmailParseResult errorCode = KPIM::isValidEmailAddress( *it ); if ( errorCode != KPIM::AddressOk ) { brokenAddress = ( *it ); return errorCode; } } return KPIM::AddressOk; } //----------------------------------------------------------------------------- const DwString& KMMessage::asDwString() const { if (mNeedsAssembly) { mNeedsAssembly = false; mMsg->Assemble(); } return mMsg->AsString(); } //----------------------------------------------------------------------------- const DwMessage* KMMessage::asDwMessage() { if (mNeedsAssembly) { mNeedsAssembly = false; mMsg->Assemble(); } return mMsg; } //----------------------------------------------------------------------------- TQCString KMMessage::asString() const { return KMail::Util::CString( asDwString() ); } TQByteArray KMMessage::asSendableString() const { KMMessage msg( new DwMessage( *this->mMsg ) ); msg.removePrivateHeaderFields(); msg.removeHeaderField("Bcc"); return KMail::Util::ByteArray( msg.asDwString() ); // and another copy again! } TQCString KMMessage::headerAsSendableString() const { KMMessage msg( new DwMessage( *this->mMsg ) ); msg.removePrivateHeaderFields(); msg.removeHeaderField("Bcc"); return msg.headerAsString().latin1(); } void KMMessage::removePrivateHeaderFields() { removeHeaderField("Status"); removeHeaderField("X-Status"); removeHeaderField("X-KMail-EncryptionState"); removeHeaderField("X-KMail-SignatureState"); removeHeaderField("X-KMail-MDN-Sent"); removeHeaderField("X-KMail-Transport"); removeHeaderField("X-KMail-Identity"); removeHeaderField("X-KMail-Fcc"); removeHeaderField("X-KMail-Redirect-From"); removeHeaderField("X-KMail-Link-Message"); removeHeaderField("X-KMail-Link-Type"); removeHeaderField( "X-KMail-Markup" ); } //----------------------------------------------------------------------------- void KMMessage::setStatusFields() { char str[2] = { 0, 0 }; setHeaderField("Status", status() & KMMsgStatusNew ? "R" : "RO"); setHeaderField("X-Status", statusToStr(status())); str[0] = (char)encryptionState(); setHeaderField("X-KMail-EncryptionState", str); str[0] = (char)signatureState(); //kdDebug(5006) << "Setting SignatureState header field to " << str[0] << endl; setHeaderField("X-KMail-SignatureState", str); str[0] = static_cast( mdnSentState() ); setHeaderField("X-KMail-MDN-Sent", str); // We better do the assembling ourselves now to prevent the // mimelib from changing the message *body*. (khz, 10.8.2002) mNeedsAssembly = false; mMsg->Headers().Assemble(); mMsg->Assemble( mMsg->Headers(), mMsg->Body() ); } //---------------------------------------------------------------------------- TQString KMMessage::headerAsString() const { DwHeaders& header = mMsg->Headers(); header.Assemble(); if ( header.AsString().empty() ) return TQString(); return TQString::fromLatin1( header.AsString().c_str() ); } //----------------------------------------------------------------------------- DwMediaType& KMMessage::dwContentType() { return mMsg->Headers().ContentType(); } void KMMessage::fromByteArray( const TQByteArray & ba, bool setStatus ) { return fromDwString( DwString( ba.data(), ba.size() ), setStatus ); } void KMMessage::fromString( const TQCString & str, bool aSeStatus ) { return fromDwString( KMail::Util::dwString( str ), aSeStatus ); } void KMMessage::fromDwString(const DwString& str, bool aSeStatus) { delete mMsg; mMsg = new DwMessage; mMsg->FromString( str ); mMsg->Parse(); if (aSeStatus) { setStatus(headerField("Status").latin1(), headerField("X-Status").latin1()); setEncryptionStateChar( headerField("X-KMail-EncryptionState").at(0) ); setSignatureStateChar( headerField("X-KMail-SignatureState").at(0) ); setMDNSentState( static_cast( headerField("X-KMail-MDN-Sent").at(0).latin1() ) ); } if ( invitationState() == KMMsgInvitationUnknown && readyToShow() ) updateInvitationState(); if ( attachmentState() == KMMsgAttachmentUnknown && readyToShow() ) updateAttachmentState(); mNeedsAssembly = false; mDate = date(); } //----------------------------------------------------------------------------- TQString KMMessage::formatString(const TQString& aStr) const { TQString result, str; TQChar ch; uint j; if (aStr.isEmpty()) return aStr; unsigned int strLength(aStr.length()); for (uint i=0; i' '; j++) ; unsigned int strLength(str.length()); for (; j < strLength && str[j] <= ' '; j++) ; result += str[0]; if (str[j]>' ') result += str[j]; else if (str[1]>' ') result += str[1]; } break; case 'T': result += toStrip(); break; case 't': result += to(); break; case 'C': result += ccStrip(); break; case 'c': result += cc(); break; case 'S': result += subject(); break; case '_': result += ' '; break; case 'L': result += "\n"; break; case '%': result += '%'; break; default: result += '%'; result += ch; break; } } else result += ch; } return result; } static void removeTrailingSpace( TQString &line ) { int i = line.length()-1; while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t'))) i--; line.truncate( i+1); } static TQString splitLine( TQString &line) { removeTrailingSpace( line ); int i = 0; int j = -1; int l = line.length(); // TODO: Replace tabs with spaces first. while(i < l) { TQChar c = line[i]; if ((c == '>') || (c == ':') || (c == '|')) j = i+1; else if ((c != ' ') && (c != '\t')) break; i++; } if ( j <= 0 ) { return ""; } if ( i == l ) { TQString result = line.left(j); line = TQString(); return result; } TQString result = line.left(j); line = line.mid(j); return result; } static TQString flowText(TQString &text, const TQString& indent, int maxLength) { maxLength--; if (text.isEmpty()) { return indent+"\n"; } TQString result; while (1) { int i; if ((int) text.length() > maxLength) { i = maxLength; while( (i >= 0) && (text[i] != ' ')) i--; if (i <= 0) { // Couldn't break before maxLength. i = maxLength; // while( (i < (int) text.length()) && (text[i] != ' ')) // i++; } } else { i = text.length(); } TQString line = text.left(i); if (i < (int) text.length()) text = text.mid(i); else text = TQString(); result += indent + line + '\n'; if (text.isEmpty()) return result; } } static bool flushPart(TQString &msg, TQStringList &part, const TQString &indent, int maxLength) { maxLength -= indent.length(); if (maxLength < 20) maxLength = 20; // Remove empty lines at end of quote while ((part.begin() != part.end()) && part.last().isEmpty()) { part.remove(part.fromLast()); } TQString text; for(TQStringList::Iterator it2 = part.begin(); it2 != part.end(); it2++) { TQString line = (*it2); if (line.isEmpty()) { if (!text.isEmpty()) msg += flowText(text, indent, maxLength); msg += indent + '\n'; } else { if (text.isEmpty()) text = line; else text += ' '+line.stripWhiteSpace(); if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10))) msg += flowText(text, indent, maxLength); } } if (!text.isEmpty()) msg += flowText(text, indent, maxLength); bool appendEmptyLine = true; if (!part.count()) appendEmptyLine = false; part.clear(); return appendEmptyLine; } static TQString stripSignature( const TQString & msg, bool clearSigned ) { if ( clearSigned ) return msg.left( msg.findRev( TQRegExp( "\n--\\s?\n" ) ) ); else return msg.left( msg.findRev( "\n-- \n" ) ); } TQString KMMessage::smartQuote( const TQString & msg, int maxLineLength ) { TQStringList part; TQString oldIndent; bool firstPart = true; const TQStringList lines = TQStringList::split('\n', msg, true); TQString result; for(TQStringList::const_iterator it = lines.begin(); it != lines.end(); ++it) { TQString line = *it; const TQString indent = splitLine( line ); if ( line.isEmpty()) { if (!firstPart) part.append(TQString()); continue; }; if (firstPart) { oldIndent = indent; firstPart = false; } if (oldIndent != indent) { TQString fromLine; // Search if the last non-blank line could be "From" line if (part.count() && (oldIndent.length() < indent.length())) { TQStringList::Iterator it2 = part.fromLast(); while( (it2 != part.end()) && (*it2).isEmpty()) --it2; if ((it2 != part.end()) && ((*it2).endsWith(":"))) { fromLine = oldIndent + (*it2) + '\n'; part.remove(it2); } } if (flushPart( result, part, oldIndent, maxLineLength)) { if (oldIndent.length() > indent.length()) result += indent + '\n'; else result += oldIndent + '\n'; } if (!fromLine.isEmpty()) { result += fromLine; } oldIndent = indent; } part.append(line); } flushPart( result, part, oldIndent, maxLineLength); return result; } //----------------------------------------------------------------------------- void KMMessage::parseTextStringFromDwPart( partNode * root, TQCString& parsedString, const TQTextCodec*& codec, bool& isHTML ) const { if ( !root ) return; isHTML = false; partNode * curNode = root->findType( DwMime::kTypeText, DwMime::kSubtypeUnknown, true, false ); kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart() - " << ( curNode ? "text part found!\n" : "sorry, no text node!\n" ) << endl; if( curNode ) { isHTML = DwMime::kSubtypeHtml == curNode->subType(); // now parse the TEXT message part we want to quote ObjectTreeParser otp( 0, 0, true, false, true ); otp.parseObjectTree( curNode ); parsedString = otp.rawReplyString(); codec = curNode->msgPart().codec(); } } //----------------------------------------------------------------------------- TQString KMMessage::asPlainTextFromObjectTree( partNode *root, bool aStripSignature, bool allowDecryption ) const { Q_ASSERT( root ); Q_ASSERT( root->processed() ); TQCString parsedString; bool isHTML = false; const TQTextCodec * codec = 0; if ( !root ) return TQString(); parseTextStringFromDwPart( root, parsedString, codec, isHTML ); if ( mOverrideCodec || !codec ) codec = this->codec(); if ( parsedString.isEmpty() ) return TQString(); bool clearSigned = false; TQString result; // decrypt if ( allowDecryption ) { TQPtrList pgpBlocks; TQStrList nonPgpBlocks; if ( Kpgp::Module::prepareMessageForDecryption( parsedString, pgpBlocks, nonPgpBlocks ) ) { // Only decrypt/strip off the signature if there is only one OpenPGP // block in the message if ( pgpBlocks.count() == 1 ) { Kpgp::Block * block = pgpBlocks.first(); if ( block->type() == Kpgp::PgpMessageBlock || block->type() == Kpgp::ClearsignedBlock ) { if ( block->type() == Kpgp::PgpMessageBlock ) { // try to decrypt this OpenPGP block block->decrypt(); } else { // strip off the signature block->verify(); clearSigned = true; } result = codec->toUnicode( nonPgpBlocks.first() ) + codec->toUnicode( block->text() ) + codec->toUnicode( nonPgpBlocks.last() ); } } } } if ( result.isEmpty() ) { result = codec->toUnicode( parsedString ); if ( result.isEmpty() ) return result; } // html -> plaintext conversion, if necessary: if ( isHTML && mDecodeHTML ) { TDEHTMLPart htmlPart; htmlPart.setOnlyLocalReferences( true ); htmlPart.setMetaRefreshEnabled( false ); htmlPart.setPluginsEnabled( false ); htmlPart.setJScriptEnabled( false ); htmlPart.setJavaEnabled( false ); htmlPart.begin(); htmlPart.write( result ); htmlPart.end(); htmlPart.selectAll(); result = htmlPart.selectedText(); } // strip the signature (footer): if ( aStripSignature ) return stripSignature( result, clearSigned ); else return result; } //----------------------------------------------------------------------------- TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const { partNode *root = partNode::fromMessage( this ); if ( !root ) return TQString(); ObjectTreeParser otp; otp.parseObjectTree( root ); TQString result = asPlainTextFromObjectTree( root, aStripSignature, allowDecryption ); delete root; return result; } TQString KMMessage::asQuotedString( const TQString& aHeaderStr, const TQString& aIndentStr, const TQString& selection /* = TQString() */, bool aStripSignature /* = true */, bool allowDecryption /* = true */) const { TQString content = selection.isEmpty() ? asPlainText( aStripSignature, allowDecryption ) : selection ; // Remove blank lines at the beginning: const int firstNonWS = content.find( TQRegExp( "\\S" ) ); const int lineStart = content.findRev( '\n', firstNonWS ); if ( lineStart >= 0 ) content.remove( 0, static_cast( lineStart ) ); const TQString indentStr = formatString( aIndentStr ); content.replace( '\n', '\n' + indentStr ); content.prepend( indentStr ); content += '\n'; const TQString headerStr = formatString( aHeaderStr ); if ( sSmartQuote && sWordWrap ) return headerStr + smartQuote( content, sWrapCol ); return headerStr + content; } //----------------------------------------------------------------------------- KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy, TQString selection /* = TQString() */, bool noQuote /* = false */, bool allowDecryption /* = true */, const TQString &tmpl /* = TQString() */, const TQString &originatingAccount /* = TQString() */ ) { KMMessage* msg = new KMMessage; TQString mailingListStr, replyToStr, toStr; TQStringList mailingListAddresses; TQCString refStr, headerName; bool replyAll = true; msg->initFromMessage(this); MailingList::name(this, headerName, mailingListStr); replyToStr = replyTo(); msg->setCharset("utf-8"); // determine the mailing list posting address if ( parent() && parent()->isMailingListEnabled() && !parent()->mailingListPostAddress().isEmpty() ) { mailingListAddresses << parent()->mailingListPostAddress(); } if ( headerField("List-Post").find( "mailto:", 0, false ) != -1 ) { TQString listPost = headerField("List-Post"); TQRegExp rx( "]+)@([^>]+)>", false ); if ( rx.search( listPost, 0 ) != -1 ) // matched mailingListAddresses << rx.cap(1) + '@' + rx.cap(2); } // use the "On ... Joe User wrote:" header by default switch( replyStrategy ) { case KMail::ReplySmart : { if ( !headerField( "Mail-Followup-To" ).isEmpty() ) { toStr = headerField( "Mail-Followup-To" ); } else if ( !replyToStr.isEmpty() ) { // assume a Reply-To header mangling mailing list toStr = replyToStr; } else if ( !mailingListAddresses.isEmpty() ) { toStr = mailingListAddresses[0]; } else { // doesn't seem to be a mailing list, reply to From: address toStr = from(); //replyStr = sReplyStr; // reply to author, so use "On ... you wrote:" replyAll = false; } // strip all my addresses from the list of recipients TQStringList recipients = KPIM::splitEmailAddrList( toStr ); toStr = stripMyAddressesFromAddressList( recipients ).join(", "); // ... unless the list contains only my addresses (reply to self) if ( toStr.isEmpty() && !recipients.isEmpty() ) toStr = recipients[0]; break; } case KMail::ReplyList : { if ( !headerField( "Mail-Followup-To" ).isEmpty() ) { toStr = headerField( "Mail-Followup-To" ); } else if ( !mailingListAddresses.isEmpty() ) { toStr = mailingListAddresses[0]; } else if ( !replyToStr.isEmpty() ) { // assume a Reply-To header mangling mailing list toStr = replyToStr; } // strip all my addresses from the list of recipients TQStringList recipients = KPIM::splitEmailAddrList( toStr ); toStr = stripMyAddressesFromAddressList( recipients ).join(", "); break; } case KMail::ReplyAll : { TQStringList recipients; TQStringList ccRecipients; // add addresses from the Reply-To header to the list of recipients if( !replyToStr.isEmpty() ) { recipients += KPIM::splitEmailAddrList( replyToStr ); // strip all possible mailing list addresses from the list of Reply-To // addresses for ( TQStringList::const_iterator it = mailingListAddresses.begin(); it != mailingListAddresses.end(); ++it ) { recipients = stripAddressFromAddressList( *it, recipients ); } } if ( !mailingListAddresses.isEmpty() ) { // this is a mailing list message if ( recipients.isEmpty() && !from().isEmpty() ) { // The sender didn't set a Reply-to address, so we add the From // address to the list of CC recipients. ccRecipients += from(); kdDebug(5006) << "Added " << from() << " to the list of CC recipients" << endl; } // if it is a mailing list, add the posting address recipients.prepend( mailingListAddresses[0] ); } else { // this is a normal message if ( recipients.isEmpty() && !from().isEmpty() ) { // in case of replying to a normal message only then add the From // address to the list of recipients if there was no Reply-to address recipients += from(); kdDebug(5006) << "Added " << from() << " to the list of recipients" << endl; } } // strip all my addresses from the list of recipients toStr = stripMyAddressesFromAddressList( recipients ).join(", "); // merge To header and CC header into a list of CC recipients if( !cc().isEmpty() || !to().isEmpty() ) { TQStringList list; if (!to().isEmpty()) list += KPIM::splitEmailAddrList(to()); if (!cc().isEmpty()) list += KPIM::splitEmailAddrList(cc()); for( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) { if( !addressIsInAddressList( *it, recipients ) && !addressIsInAddressList( *it, ccRecipients ) ) { ccRecipients += *it; kdDebug(5006) << "Added " << *it << " to the list of CC recipients" << endl; } } } if ( !ccRecipients.isEmpty() ) { // strip all my addresses from the list of CC recipients ccRecipients = stripMyAddressesFromAddressList( ccRecipients ); // in case of a reply to self toStr might be empty. if that's the case // then propagate a cc recipient to To: (if there is any). if ( toStr.isEmpty() && !ccRecipients.isEmpty() ) { toStr = ccRecipients[0]; ccRecipients.pop_front(); } msg->setCc( ccRecipients.join(", ") ); } if ( toStr.isEmpty() && !recipients.isEmpty() ) { // reply to self without other recipients toStr = recipients[0]; } break; } case KMail::ReplyAuthor : { if ( !replyToStr.isEmpty() ) { TQStringList recipients = KPIM::splitEmailAddrList( replyToStr ); // strip the mailing list post address from the list of Reply-To // addresses since we want to reply in private for ( TQStringList::const_iterator it = mailingListAddresses.begin(); it != mailingListAddresses.end(); ++it ) { recipients = stripAddressFromAddressList( *it, recipients ); } if ( !recipients.isEmpty() ) { toStr = recipients.join(", "); } else { // there was only the mailing list post address in the Reply-To header, // so use the From address instead toStr = from(); } } else if ( !from().isEmpty() ) { toStr = from(); } replyAll = false; break; } case KMail::ReplyNone : { // the addressees will be set by the caller } } if (!originatingAccount.isEmpty()) { msg->setOriginatingAccountName(originatingAccount); } msg->setTo(toStr); refStr = getRefStr(); if (!refStr.isEmpty()) msg->setReferences(refStr); //In-Reply-To = original msg-id msg->setReplyToId(msgId()); // if (!noQuote) { // if( selectionIsBody ){ // TQCString cStr = selection.latin1(); // msg->setBody( cStr ); // }else{ // msg->setBody(asQuotedString(replyStr + "\n", sIndentPrefixStr, selection, // sSmartQuote, allowDecryption).utf8()); // } // } msg->setSubject( replySubject() ); msg->setHeaderField( "X-KMail-QuotePrefix", formatString( GlobalSettings::self()->quoteString() ) ); if( !noQuote ) { TemplateParser parser( msg, ( replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply ) ); parser.setAllowDecryption( allowDecryption ); if ( GlobalSettings::quoteSelectionOnly() ) { parser.setSelection( selection ); } if ( !tmpl.isEmpty() ) { parser.process( tmpl, this ); } else { parser.process( this ); } } // setStatus(KMMsgStatusReplied); msg->link(this, KMMsgStatusReplied); if ( parent() && parent()->putRepliesInSameFolder() ) msg->setFcc( parent()->idString() ); // replies to an encrypted message should be encrypted as well if ( encryptionState() == KMMsgPartiallyEncrypted || encryptionState() == KMMsgFullyEncrypted ) { msg->setEncryptionState( KMMsgFullyEncrypted ); } return msg; } //----------------------------------------------------------------------------- TQCString KMMessage::getRefStr() const { TQCString firstRef, lastRef, refStr, retRefStr; int i, j; refStr = headerField("References").stripWhiteSpace().latin1(); if (refStr.isEmpty()) return headerField("Message-Id").latin1(); i = refStr.find('<'); j = refStr.find('>'); firstRef = refStr.mid(i, j-i+1); if (!firstRef.isEmpty()) retRefStr = firstRef + ' '; i = refStr.findRev('<'); j = refStr.findRev('>'); lastRef = refStr.mid(i, j-i+1); if (!lastRef.isEmpty() && lastRef != firstRef) retRefStr += lastRef + ' '; retRefStr += headerField("Message-Id").latin1(); return retRefStr; } KMMessage* KMMessage::createRedirect( const TQString &toStr ) { // copy the message 1:1 KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) ); KMMessagePart msgPart; uint id = 0; TQString strId = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace(); if ( !strId.isEmpty()) id = strId.toUInt(); const KPIM::Identity & ident = kmkernel->identityManager()->identityForUoidOrDefault( id ); // X-KMail-Redirect-From: content TQString strByWayOf = TQString("%1 (by way of %2 <%3>)") .arg( from() ) .arg( ident.fullName() ) .arg( ident.primaryEmailAddress() ); // Resent-From: content TQString strFrom = TQString("%1 <%2>") .arg( ident.fullName() ) .arg( ident.primaryEmailAddress() ); // format the current date to be used in Resent-Date: TQString origDate = msg->headerField( "Date" ); msg->setDateToday(); TQString newDate = msg->headerField( "Date" ); // make sure the Date: header is valid if ( origDate.isEmpty() ) msg->removeHeaderField( "Date" ); else msg->setHeaderField( "Date", origDate ); // prepend Resent-*: headers (c.f. RFC2822 3.6.6) msg->setHeaderField( "Resent-Message-ID", generateMessageId( msg->sender() ), Structured, true); msg->setHeaderField( "Resent-Date", newDate, Structured, true ); msg->setHeaderField( "Resent-To", toStr, Address, true ); msg->setHeaderField( "Resent-From", strFrom, Address, true ); msg->setHeaderField( "X-KMail-Redirect-From", strByWayOf ); msg->setHeaderField( "X-KMail-Recipients", toStr, Address ); msg->link(this, KMMsgStatusForwarded); return msg; } //----------------------------------------------------------------------------- TQCString KMMessage::createForwardBody() { TQString s; TQCString str; if (sHeaderStrategy == HeaderStrategy::all()) { s = "\n\n---------- " + sForwardStr + " ----------\n\n"; s += headerAsString(); str = asQuotedString(s, "", TQString(), false, false).utf8(); str += "\n-------------------------------------------------------\n"; } else { s = "\n\n---------- " + sForwardStr + " ----------\n\n"; s += "Subject: " + subject() + "\n"; s += "Date: " + KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized, date(), sReplyLanguage, false ) + "\n"; s += "From: " + from() + "\n"; s += "To: " + to() + "\n"; if (!cc().isEmpty()) s += "Cc: " + cc() + "\n"; s += "\n"; str = asQuotedString(s, "", TQString(), false, false).utf8(); str += "\n-------------------------------------------------------\n"; } return str; } void KMMessage::sanitizeHeaders( const TQStringList& whiteList ) { // Strip out all headers apart from the content description and other // whitelisted ones, because we don't want to inherit them. DwHeaders& header = mMsg->Headers(); DwField* field = header.FirstField(); DwField* nextField; while (field) { nextField = field->Next(); if ( field->FieldNameStr().find( "ontent" ) == DwString::npos && !whiteList.contains( TQString::fromLatin1( field->FieldNameStr().c_str() ) ) ) header.RemoveField(field); field = nextField; } mMsg->Assemble(); } //----------------------------------------------------------------------------- KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString() */ ) { KMMessage* msg = new KMMessage(); // If this is a multipart mail or if the main part is only the text part, // Make an identical copy of the mail, minus headers, so attachments are // preserved if ( type() == DwMime::kTypeMultipart || ( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypePlain ) ) { // ## slow, we could probably use: delete msg->mMsg; msg->mMsg = new DwMessage( this->mMsg ); msg->fromDwString( this->asDwString() ); // remember the type and subtype, initFromMessage sets the contents type to // text/plain, via initHeader, for unclear reasons DwMediaType oldContentType = msg->mMsg->Headers().ContentType(); msg->sanitizeHeaders(); // strip blacklisted parts TQStringList blacklist = GlobalSettings::self()->mimetypesToStripWhenInlineForwarding(); for ( TQStringList::Iterator it = blacklist.begin(); it != blacklist.end(); ++it ) { TQString entry = (*it); int sep = entry.find( '/' ); TQCString type = entry.left( sep ).latin1(); TQCString subtype = entry.mid( sep+1 ).latin1(); kdDebug( 5006 ) << "Looking for blacklisted type: " << type << "/" << subtype << endl; while ( DwBodyPart * part = msg->findDwBodyPart( type, subtype ) ) { msg->mMsg->Body().RemoveBodyPart( part ); } } msg->mMsg->Assemble(); msg->initFromMessage( this ); //restore type msg->mMsg->Headers().ContentType().FromString( oldContentType.AsString() ); msg->mMsg->Headers().ContentType().Parse(); msg->mMsg->Assemble(); } else if( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypeHtml ) { // This is non-multipart html mail. Let`s make it text/plain and allow // template parser do the hard job. msg->initFromMessage( this ); msg->setType( DwMime::kTypeText ); msg->setSubtype( DwMime::kSubtypeHtml ); msg->mNeedsAssembly = true; msg->cleanupHeader(); } else { // This is a non-multipart, non-text mail (e.g. text/calendar). Construct // a multipart/mixed mail and add the original body as an attachment. msg->initFromMessage( this ); msg->removeHeaderField("Content-Type"); msg->removeHeaderField("Content-Transfer-Encoding"); // Modify the ContentType directly (replaces setAutomaticFields(true)) DwHeaders & header = msg->mMsg->Headers(); header.MimeVersion().FromString("1.0"); DwMediaType & contentType = msg->dwContentType(); contentType.SetType( DwMime::kTypeMultipart ); contentType.SetSubtype( DwMime::kSubtypeMixed ); contentType.CreateBoundary(0); contentType.Assemble(); // empty text part KMMessagePart msgPart; bodyPart( 0, &msgPart ); msg->addBodyPart(&msgPart); // the old contents of the mail KMMessagePart secondPart; secondPart.setType( type() ); secondPart.setSubtype( subtype() ); secondPart.setBody( mMsg->Body().AsString() ); // use the headers of the original mail applyHeadersToMessagePart( mMsg->Headers(), &secondPart ); msg->addBodyPart(&secondPart); msg->mNeedsAssembly = true; msg->cleanupHeader(); } // TQString st = TQString::fromUtf8(createForwardBody()); msg->setSubject( forwardSubject() ); TemplateParser parser( msg, TemplateParser::Forward ); if ( !tmpl.isEmpty() ) { parser.process( tmpl, this ); } else { parser.process( this ); } // TQCString encoding = autoDetectCharset(charset(), sPrefCharsets, msg->body()); // if (encoding.isEmpty()) encoding = "utf-8"; // msg->setCharset(encoding); // force utf-8 // msg->setCharset( "utf-8" ); msg->link(this, KMMsgStatusForwarded); return msg; } static const struct { const char * dontAskAgainID; bool canDeny; const char * text; } mdnMessageBoxes[] = { { "mdnNormalAsk", true, I18N_NOOP("This message contains a request to return a notification " "about your reception of the message.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, { "mdnUnknownOption", false, I18N_NOOP("This message contains a request to send a notification " "about your reception of the message.\n" "It contains a processing instruction that is marked as " "\"required\", but which is unknown to KMail.\n" "You can either ignore the request or let KMail send a " "\"failed\" response.") }, { "mdnMultipleAddressesInReceiptTo", true, I18N_NOOP("This message contains a request to send a notification " "about your reception of the message,\n" "but it is requested to send the notification to more " "than one address.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, { "mdnReturnPathEmpty", true, I18N_NOOP("This message contains a request to send a notification " "about your reception of the message,\n" "but there is no return-path set.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, { "mdnReturnPathNotInReceiptTo", true, I18N_NOOP("This message contains a request to send a notification " "about your reception of the message,\n" "but the return-path address differs from the address " "the notification was requested to be sent to.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, }; static const int numMdnMessageBoxes = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes; static int requestAdviceOnMDN( const char * what ) { for ( int i = 0 ; i < numMdnMessageBoxes ; ++i ) { if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) ) { if ( mdnMessageBoxes[i].canDeny ) { const KCursorSaver saver( TQCursor::ArrowCursor ); int answer = TQMessageBox::information( 0, i18n("Message Disposition Notification Request"), i18n( mdnMessageBoxes[i].text ), i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") ); return answer ? answer + 1 : 0 ; // map to "mode" in createMDN } else { const KCursorSaver saver( TQCursor::ArrowCursor ); int answer = TQMessageBox::information( 0, i18n("Message Disposition Notification Request"), i18n( mdnMessageBoxes[i].text ), i18n("&Ignore"), i18n("&Send") ); return answer ? answer + 2 : 0 ; // map to "mode" in createMDN } } } kdWarning(5006) << "didn't find data for message box \"" << what << "\"" << endl; return 0; } KMMessage* KMMessage::createMDN( MDN::ActionMode a, MDN::DispositionType d, bool allowGUI, TQValueList m ) { // RFC 2298: At most one MDN may be issued on behalf of each // particular recipient by their user agent. That is, once an MDN // has been issued on behalf of a recipient, no further MDNs may be // issued on behalf of that recipient, even if another disposition // is performed on the message. //#define MDN_DEBUG 1 #ifndef MDN_DEBUG if ( mdnSentState() != KMMsgMDNStateUnknown && mdnSentState() != KMMsgMDNNone ) return 0; #else char st[2]; st[0] = (char)mdnSentState(); st[1] = 0; kdDebug(5006) << "mdnSentState() == '" << st << "'" << endl; #endif // RFC 2298: An MDN MUST NOT be generated in response to an MDN. if ( findDwBodyPart( DwMime::kTypeMessage, DwMime::kSubtypeDispositionNotification ) ) { setMDNSentState( KMMsgMDNIgnore ); return 0; } // extract where to send to: TQString receiptTo = headerField("Disposition-Notification-To"); if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0; receiptTo.remove( '\n' ); MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user TQString special; // fill in case of error, warning or failure TDEConfigGroup mdnConfig( KMKernel::config(), "MDN" ); // default: int mode = mdnConfig.readNumEntry( "default-policy", 0 ); if ( !mode || mode < 0 || mode > 3 ) { // early out for ignore: setMDNSentState( KMMsgMDNIgnore ); return 0; } // RFC 2298: An importance of "required" indicates that // interpretation of the parameter is necessary for proper // generation of an MDN in response to this request. If a UA does // not understand the meaning of the parameter, it MUST NOT generate // an MDN with any disposition type other than "failed" in response // to the request. TQString notificationOptions = headerField("Disposition-Notification-Options"); if ( notificationOptions.contains( "required", false ) ) { // ### hacky; should parse... // There is a required option that we don't understand. We need to // ask the user what we should do: if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( "mdnUnknownOption" ); s = MDN::SentManually; special = i18n("Header \"Disposition-Notification-Options\" contained " "required, but unknown parameter"); d = MDN::Failed; m.clear(); // clear modifiers } // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no // MDN sent) ] if there is more than one distinct address in the // Disposition-Notification-To header. kdDebug(5006) << "KPIM::splitEmailAddrList(receiptTo): " << KPIM::splitEmailAddrList(receiptTo).join("\n") << endl; if ( KPIM::splitEmailAddrList(receiptTo).count() > 1 ) { if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( "mdnMultipleAddressesInReceiptTo" ); s = MDN::SentManually; } // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in // the Disposition-Notification-To header differs from the address // in the Return-Path header. [...] Confirmation from the user // SHOULD be obtained (or no MDN sent) if there is no Return-Path // header in the message [...] AddrSpecList returnPathList = extractAddrSpecs("Return-Path"); TQString returnPath = returnPathList.isEmpty() ? TQString() : returnPathList.front().localPart + '@' + returnPathList.front().domain ; kdDebug(5006) << "clean return path: " << returnPath << endl; if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, false ) ) { if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( returnPath.isEmpty() ? "mdnReturnPathEmpty" : "mdnReturnPathNotInReceiptTo" ); s = MDN::SentManually; } if ( a != KMime::MDN::AutomaticAction ) { //TODO: only ingore user settings for AutomaticAction if requested if ( mode == 1 ) { // ask if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( "mdnNormalAsk" ); s = MDN::SentManually; // asked user } switch ( mode ) { case 0: // ignore: setMDNSentState( KMMsgMDNIgnore ); return 0; default: case 1: kdFatal(5006) << "KMMessage::createMDN(): The \"ask\" mode should " << "never appear here!" << endl; break; case 2: // deny d = MDN::Denied; m.clear(); break; case 3: break; } } // extract where to send from: TQString finalRecipient = kmkernel->identityManager() ->identityForUoidOrDefault( identityUoid() ).fullEmailAddr(); // // Generate message: // KMMessage * receipt = new KMMessage(); receipt->initFromMessage( this ); receipt->removeHeaderField("Content-Type"); receipt->removeHeaderField("Content-Transfer-Encoding"); // Modify the ContentType directly (replaces setAutomaticFields(true)) DwHeaders & header = receipt->mMsg->Headers(); header.MimeVersion().FromString("1.0"); DwMediaType & contentType = receipt->dwContentType(); contentType.SetType( DwMime::kTypeMultipart ); contentType.SetSubtype( DwMime::kSubtypeReport ); contentType.CreateBoundary(0); receipt->mNeedsAssembly = true; receipt->setContentTypeParam( "report-type", "disposition-notification" ); TQString description = replaceHeadersInString( MDN::descriptionFor( d, m ) ); // text/plain part: KMMessagePart firstMsgPart; firstMsgPart.setTypeStr( "text" ); firstMsgPart.setSubtypeStr( "plain" ); firstMsgPart.setBodyFromUnicode( description ); receipt->addBodyPart( &firstMsgPart ); // message/disposition-notification part: KMMessagePart secondMsgPart; secondMsgPart.setType( DwMime::kTypeMessage ); secondMsgPart.setSubtype( DwMime::kSubtypeDispositionNotification ); //secondMsgPart.setCharset( "us-ascii" ); //secondMsgPart.setCteStr( "7bit" ); secondMsgPart.setBodyEncoded( MDN::dispositionNotificationBodyContent( finalRecipient, rawHeaderField("Original-Recipient"), id(), /* Message-ID */ d, a, s, m, special ) ); receipt->addBodyPart( &secondMsgPart ); // message/rfc822 or text/rfc822-headers body part: int num = mdnConfig.readNumEntry( "quote-message", 0 ); if ( num < 0 || num > 2 ) num = 0; MDN::ReturnContent returnContent = static_cast( num ); KMMessagePart thirdMsgPart; switch ( returnContent ) { case MDN::All: thirdMsgPart.setTypeStr( "message" ); thirdMsgPart.setSubtypeStr( "rfc822" ); thirdMsgPart.setBody( asSendableString() ); receipt->addBodyPart( &thirdMsgPart ); break; case MDN::HeadersOnly: thirdMsgPart.setTypeStr( "text" ); thirdMsgPart.setSubtypeStr( "rfc822-headers" ); thirdMsgPart.setBody( headerAsSendableString() ); receipt->addBodyPart( &thirdMsgPart ); break; case MDN::Nothing: default: break; }; receipt->setTo( receiptTo ); receipt->setSubject( "Message Disposition Notification" ); receipt->setReplyToId( msgId() ); receipt->setReferences( getRefStr() ); receipt->cleanupHeader(); kdDebug(5006) << "final message:\n" + receipt->asString() << endl; // // Set "MDN sent" status: // KMMsgMDNSentState state = KMMsgMDNStateUnknown; switch ( d ) { case MDN::Displayed: state = KMMsgMDNDisplayed; break; case MDN::Deleted: state = KMMsgMDNDeleted; break; case MDN::Dispatched: state = KMMsgMDNDispatched; break; case MDN::Processed: state = KMMsgMDNProcessed; break; case MDN::Denied: state = KMMsgMDNDenied; break; case MDN::Failed: state = KMMsgMDNFailed; break; }; setMDNSentState( state ); return receipt; } TQString KMMessage::replaceHeadersInString( const TQString & s ) const { TQString result = s; TQRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false ); Q_ASSERT( rx.isValid() ); TQRegExp rxDate( "\\$\\{date\\}" ); Q_ASSERT( rxDate.isValid() ); TQString sDate = KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized, date() ); int idx = 0; if( ( idx = rxDate.search( result, idx ) ) != -1 ) { result.replace( idx, rxDate.matchedLength(), sDate ); } idx = 0; while ( ( idx = rx.search( result, idx ) ) != -1 ) { TQString replacement = headerField( TQString(rx.cap(1)).latin1() ); result.replace( idx, rx.matchedLength(), replacement ); idx += replacement.length(); } return result; } KMMessage* KMMessage::createDeliveryReceipt() const { TQString str, receiptTo; KMMessage *receipt; receiptTo = headerField("Disposition-Notification-To"); if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0; receiptTo.remove( '\n' ); receipt = new KMMessage; receipt->initFromMessage(this); receipt->setTo(receiptTo); receipt->setSubject(i18n("Receipt: ") + subject()); str = "Your message was successfully delivered."; str += "\n\n---------- Message header follows ----------\n"; str += headerAsString(); str += "--------------------------------------------\n"; // Conversion to latin1 is correct here as Mail headers should contain // ascii only receipt->setBody(str.latin1()); receipt->setAutomaticFields(); return receipt; } void KMMessage::applyIdentity( uint id ) { const KPIM::Identity & ident = kmkernel->identityManager()->identityForUoidOrDefault( id ); if(ident.fullEmailAddr().isEmpty()) setFrom(""); else setFrom(ident.fullEmailAddr()); if(ident.replyToAddr().isEmpty()) setReplyTo(""); else setReplyTo(ident.replyToAddr()); if(ident.bcc().isEmpty()) setBcc(""); else setBcc(ident.bcc()); if (ident.organization().isEmpty()) removeHeaderField("Organization"); else setHeaderField("Organization", ident.organization()); if (ident.isDefault()) removeHeaderField("X-KMail-Identity"); else setHeaderField("X-KMail-Identity", TQString::number( ident.uoid() )); if ( ident.transport().isEmpty() ) removeHeaderField( "X-KMail-Transport" ); else setHeaderField( "X-KMail-Transport", ident.transport() ); if ( ident.fcc().isEmpty() ) setFcc( TQString() ); else setFcc( ident.fcc() ); if ( ident.drafts().isEmpty() ) setDrafts( TQString() ); else setDrafts( ident.drafts() ); if ( ident.templates().isEmpty() ) setTemplates( TQString() ); else setTemplates( ident.templates() ); } //----------------------------------------------------------------------------- void KMMessage::initHeader( uint id ) { applyIdentity( id ); setTo(""); setSubject(""); setDateToday(); setHeaderField("User-Agent", "KMail/" KMAIL_VERSION ); // This will allow to change Content-Type: setHeaderField("Content-Type","text/plain"); } uint KMMessage::identityUoid() const { TQString idString = headerField("X-KMail-Identity").stripWhiteSpace(); bool ok = false; int id = idString.toUInt( &ok ); if ( !ok || id == 0 ) id = kmkernel->identityManager()->identityForAddress( to() + ", " + cc() ).uoid(); if ( id == 0 && parent() ) id = parent()->identity(); return id; } //----------------------------------------------------------------------------- void KMMessage::initFromMessage(const KMMessage *msg, bool idHeaders) { uint id = msg->identityUoid(); if ( idHeaders ) initHeader(id); else setHeaderField("X-KMail-Identity", TQString::number(id)); if (!msg->headerField("X-KMail-Transport").isEmpty()) setHeaderField("X-KMail-Transport", msg->headerField("X-KMail-Transport")); } //----------------------------------------------------------------------------- void KMMessage::cleanupHeader() { DwHeaders& header = mMsg->Headers(); DwField* field = header.FirstField(); DwField* nextField; if (mNeedsAssembly) mMsg->Assemble(); mNeedsAssembly = false; while (field) { nextField = field->Next(); if (field->FieldBody()->AsString().empty()) { header.RemoveField(field); mNeedsAssembly = true; } field = nextField; } } //----------------------------------------------------------------------------- void KMMessage::setAutomaticFields(bool aIsMulti) { DwHeaders& header = mMsg->Headers(); header.MimeVersion().FromString("1.0"); if (aIsMulti || numBodyParts() > 1) { // Set the type to 'Multipart' and the subtype to 'Mixed' DwMediaType& contentType = dwContentType(); contentType.SetType( DwMime::kTypeMultipart); contentType.SetSubtype(DwMime::kSubtypeMixed ); // Create a random printable string and set it as the boundary parameter contentType.CreateBoundary(0); } mNeedsAssembly = true; } //----------------------------------------------------------------------------- TQString KMMessage::dateStr() const { TDEConfigGroup general( KMKernel::config(), "General" ); DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); //kdDebug(5006)<<"#### Date = "<(general.readNumEntry( "dateFormat", KMime::DateFormatter::Fancy )), unixTime, general.readEntry( "customDateFormat" )); } //----------------------------------------------------------------------------- TQCString KMMessage::dateShortStr() const { DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); TQCString result = ctime(&unixTime); int len = result.length(); if (result[len-1]=='\n') result.truncate(len-1); return result; } //----------------------------------------------------------------------------- TQString KMMessage::dateIsoStr() const { DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); char cstr[64]; strftime(cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&unixTime)); return TQString(cstr); } //----------------------------------------------------------------------------- time_t KMMessage::date() const { time_t res = ( time_t )-1; DwHeaders& header = mMsg->Headers(); if (header.HasDate()) res = header.Date().AsUnixTime(); return res; } //----------------------------------------------------------------------------- void KMMessage::setDateToday() { struct timeval tval; gettimeofday(&tval, 0); setDate((time_t)tval.tv_sec); } //----------------------------------------------------------------------------- void KMMessage::setDate(time_t aDate) { mDate = aDate; mMsg->Headers().Date().FromCalendarTime(aDate); mMsg->Headers().Date().Assemble(); mNeedsAssembly = true; mDirty = true; } //----------------------------------------------------------------------------- void KMMessage::setDate(const TQCString& aStr) { DwHeaders& header = mMsg->Headers(); header.Date().FromString(aStr); header.Date().Parse(); mNeedsAssembly = true; mDirty = true; if (header.HasDate()) mDate = header.Date().AsUnixTime(); } //----------------------------------------------------------------------------- TQString KMMessage::to() const { // handle To same as Cc below, bug 80747 TQValueList rawHeaders = rawHeaderFields( "To" ); TQStringList headers; for ( TQValueList::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { headers << *it; } return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) ); } //----------------------------------------------------------------------------- void KMMessage::setTo(const TQString& aStr) { setHeaderField( "To", aStr, Address ); } //----------------------------------------------------------------------------- TQString KMMessage::toStrip() const { return stripEmailAddr( to() ); } //----------------------------------------------------------------------------- TQString KMMessage::replyTo() const { return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Reply-To") ); } //----------------------------------------------------------------------------- void KMMessage::setReplyTo(const TQString& aStr) { setHeaderField( "Reply-To", aStr, Address ); } //----------------------------------------------------------------------------- void KMMessage::setReplyTo(KMMessage* aMsg) { setHeaderField( "Reply-To", aMsg->from(), Address ); } //----------------------------------------------------------------------------- TQString KMMessage::cc() const { // get the combined contents of all Cc headers (as workaround for invalid // messages with multiple Cc headers) TQValueList rawHeaders = rawHeaderFields( "Cc" ); TQStringList headers; for ( TQValueList::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { headers << *it; } return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) ); } //----------------------------------------------------------------------------- void KMMessage::setCc(const TQString& aStr) { setHeaderField( "Cc", aStr, Address ); } //----------------------------------------------------------------------------- TQString KMMessage::ccStrip() const { return stripEmailAddr( cc() ); } //----------------------------------------------------------------------------- TQString KMMessage::bcc() const { return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Bcc") ); } //----------------------------------------------------------------------------- void KMMessage::setBcc(const TQString& aStr) { setHeaderField( "Bcc", aStr, Address ); } //----------------------------------------------------------------------------- TQString KMMessage::fcc() const { return headerField( "X-KMail-Fcc" ); } //----------------------------------------------------------------------------- void KMMessage::setFcc( const TQString &aStr ) { setHeaderField( "X-KMail-Fcc", aStr ); } //----------------------------------------------------------------------------- void KMMessage::setDrafts( const TQString &aStr ) { mDrafts = aStr; } //----------------------------------------------------------------------------- void KMMessage::setTemplates( const TQString &aStr ) { mTemplates = aStr; } //----------------------------------------------------------------------------- TQString KMMessage::who() const { if (mParent) return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField(mParent->whoField().utf8()) ); return from(); } //----------------------------------------------------------------------------- TQString KMMessage::from() const { return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("From") ); } //----------------------------------------------------------------------------- void KMMessage::setFrom(const TQString& bStr) { TQString aStr = bStr; if (aStr.isNull()) aStr = ""; setHeaderField( "From", aStr, Address ); mDirty = true; } //----------------------------------------------------------------------------- TQString KMMessage::fromStrip() const { return stripEmailAddr( from() ); } //----------------------------------------------------------------------------- TQString KMMessage::sender() const { AddrSpecList asl = extractAddrSpecs( "Sender" ); if ( asl.empty() ) asl = extractAddrSpecs( "From" ); if ( asl.empty() ) return TQString(); return asl.front().asString(); } //----------------------------------------------------------------------------- TQString KMMessage::subject() const { return headerField("Subject"); } //----------------------------------------------------------------------------- void KMMessage::setSubject(const TQString& aStr) { setHeaderField("Subject",aStr); mDirty = true; } //----------------------------------------------------------------------------- TQString KMMessage::xmark() const { return headerField("X-KMail-Mark"); } //----------------------------------------------------------------------------- void KMMessage::setXMark(const TQString& aStr) { setHeaderField("X-KMail-Mark", aStr); mDirty = true; } //----------------------------------------------------------------------------- TQString KMMessage::replyToId() const { int leftAngle, rightAngle; TQString replyTo, references; replyTo = headerField("In-Reply-To"); // search the end of the (first) message id in the In-Reply-To header rightAngle = replyTo.find( '>' ); if (rightAngle != -1) replyTo.truncate( rightAngle + 1 ); // now search the start of the message id leftAngle = replyTo.findRev( '<' ); if (leftAngle != -1) replyTo = replyTo.mid( leftAngle ); // if we have found a good message id we can return immediately // We ignore mangled In-Reply-To headers which are created by a // misconfigured Mutt. They look like this <"from foo"@bar.baz>, i.e. // they contain double quotes and spaces. We only check for '"'. if (!replyTo.isEmpty() && (replyTo[0] == '<') && ( -1 == replyTo.find( '"' ) ) ) return replyTo; references = headerField("References"); leftAngle = references.findRev( '<' ); if (leftAngle != -1) references = references.mid( leftAngle ); rightAngle = references.find( '>' ); if (rightAngle != -1) references.truncate( rightAngle + 1 ); // if we found a good message id in the References header return it if (!references.isEmpty() && references[0] == '<') return references; // else return the broken message id we found in the In-Reply-To header else return replyTo; } //----------------------------------------------------------------------------- TQString KMMessage::replyToIdMD5() const { return base64EncodedMD5( replyToId() ); } //----------------------------------------------------------------------------- TQString KMMessage::references() const { int leftAngle, rightAngle; TQString references = headerField( "References" ); // keep the last two entries for threading leftAngle = references.findRev( '<' ); leftAngle = references.findRev( '<', leftAngle - 1 ); if( leftAngle != -1 ) references = references.mid( leftAngle ); rightAngle = references.findRev( '>' ); if( rightAngle != -1 ) references.truncate( rightAngle + 1 ); if( !references.isEmpty() && references[0] == '<' ) return references; else return TQString(); } //----------------------------------------------------------------------------- TQString KMMessage::replyToAuxIdMD5() const { TQString result = references(); // references contains two items, use the first one // (the second to last reference) const int rightAngle = result.find( '>' ); if( rightAngle != -1 ) result.truncate( rightAngle + 1 ); return base64EncodedMD5( result ); } //----------------------------------------------------------------------------- TQString KMMessage::strippedSubjectMD5() const { return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ ); } //----------------------------------------------------------------------------- TQString KMMessage::subjectMD5() const { return base64EncodedMD5( subject(), true /*utf8*/ ); } //----------------------------------------------------------------------------- bool KMMessage::subjectIsPrefixed() const { return subjectMD5() != strippedSubjectMD5(); } //----------------------------------------------------------------------------- void KMMessage::setReplyToId(const TQString& aStr) { setHeaderField("In-Reply-To", aStr); mDirty = true; } //----------------------------------------------------------------------------- TQString KMMessage::msgId() const { TQString msgId = headerField("Message-Id"); // search the end of the message id const int rightAngle = msgId.find( '>' ); if (rightAngle != -1) msgId.truncate( rightAngle + 1 ); // now search the start of the message id const int leftAngle = msgId.findRev( '<' ); if (leftAngle != -1) msgId = msgId.mid( leftAngle ); return msgId; } //----------------------------------------------------------------------------- TQString KMMessage::msgIdMD5() const { return base64EncodedMD5( msgId() ); } //----------------------------------------------------------------------------- void KMMessage::setMsgId(const TQString& aStr) { setHeaderField("Message-Id", aStr); mDirty = true; } //----------------------------------------------------------------------------- size_t KMMessage::msgSizeServer() const { return headerField( "X-Length" ).toULong(); } //----------------------------------------------------------------------------- void KMMessage::setMsgSizeServer(size_t size) { setHeaderField("X-Length", TQCString().setNum(size)); mDirty = true; } //----------------------------------------------------------------------------- ulong KMMessage::UID() const { return headerField( "X-UID" ).toULong(); } //----------------------------------------------------------------------------- void KMMessage::setUID(ulong uid) { setHeaderField("X-UID", TQCString().setNum(uid)); mDirty = true; } //----------------------------------------------------------------------------- AddressList KMMessage::splitAddrField( const TQCString & str ) { AddressList result; const char * scursor = str.begin(); if ( !scursor ) return AddressList(); const char * const send = str.begin() + str.length(); if ( !parseAddressList( scursor, send, result ) ) kdDebug(5006) << "Error in address splitting: parseAddressList returned false!" << endl; return result; } AddressList KMMessage::headerAddrField( const TQCString & aName ) const { return KMMessage::splitAddrField( rawHeaderField( aName ) ); } AddrSpecList KMMessage::extractAddrSpecs( const TQCString & header ) const { AddressList al = headerAddrField( header ); AddrSpecList result; for ( AddressList::const_iterator ait = al.begin() ; ait != al.end() ; ++ait ) for ( MailboxList::const_iterator mit = (*ait).mailboxList.begin() ; mit != (*ait).mailboxList.end() ; ++mit ) result.push_back( (*mit).addrSpec ); return result; } TQCString KMMessage::rawHeaderField( const TQCString & name ) const { if ( name.isEmpty() ) return TQCString(); DwHeaders & header = mMsg->Headers(); DwField * field = header.FindField( name ); if ( !field ) return TQCString(); return header.FieldBody( name.data() ).AsString().c_str(); } TQValueList KMMessage::rawHeaderFields( const TQCString& field ) const { if ( field.isEmpty() || !mMsg->Headers().FindField( field ) ) return TQValueList(); std::vector v = mMsg->Headers().AllFieldBodies( field.data() ); TQValueList headerFields; for ( uint i = 0; i < v.size(); ++i ) { headerFields.append( v[i]->AsString().c_str() ); } return headerFields; } TQString KMMessage::headerField(const TQCString& aName) const { if ( aName.isEmpty() ) return TQString(); if ( !mMsg->Headers().FindField( aName ) ) return TQString(); return decodeRFC2047String( mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(), charset() ); } TQStringList KMMessage::headerFields( const TQCString& field ) const { if ( field.isEmpty() || !mMsg->Headers().FindField( field ) ) return TQStringList(); std::vector v = mMsg->Headers().AllFieldBodies( field.data() ); TQStringList headerFields; for ( uint i = 0; i < v.size(); ++i ) { headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) ); } return headerFields; } //----------------------------------------------------------------------------- void KMMessage::removeHeaderField(const TQCString& aName) { DwHeaders & header = mMsg->Headers(); DwField * field = header.FindField(aName); if (!field) return; header.RemoveField(field); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::removeHeaderFields(const TQCString& aName) { DwHeaders & header = mMsg->Headers(); while ( DwField * field = header.FindField(aName) ) { header.RemoveField(field); mNeedsAssembly = true; } } //----------------------------------------------------------------------------- void KMMessage::setHeaderField( const TQCString& aName, const TQString& bValue, HeaderFieldType type, bool prepend ) { #if 0 if ( type != Unstructured ) kdDebug(5006) << "KMMessage::setHeaderField( \"" << aName << "\", \"" << bValue << "\", " << type << " )" << endl; #endif if (aName.isEmpty()) return; DwHeaders& header = mMsg->Headers(); DwString str; DwField* field; TQCString aValue; if (!bValue.isEmpty()) { TQString value = bValue; if ( type == Address ) value = KPIM::normalizeAddressesAndEncodeIDNs( value ); #if 0 if ( type != Unstructured ) kdDebug(5006) << "value: \"" << value << "\"" << endl; #endif TQCString encoding = autoDetectCharset( charset(), sPrefCharsets, value ); if (encoding.isEmpty()) encoding = "utf-8"; aValue = encodeRFC2047String( value, encoding ); #if 0 if ( type != Unstructured ) kdDebug(5006) << "aValue: \"" << aValue << "\"" << endl; #endif } str = aName.data(); if (str[str.length()-1] != ':') str += ": "; else str += ' '; if ( !aValue.isEmpty() ) str += aValue.data(); if (str[str.length()-1] != '\n') str += '\n'; field = new DwField(str, mMsg); field->Parse(); if ( prepend ) header.AddFieldAt( 1, field ); else header.AddOrReplaceField( field ); mNeedsAssembly = true; } //----------------------------------------------------------------------------- TQCString KMMessage::typeStr() const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().TypeStr().c_str(); else return ""; } //----------------------------------------------------------------------------- int KMMessage::type() const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().Type(); else return DwMime::kTypeNull; } //----------------------------------------------------------------------------- void KMMessage::setTypeStr(const TQCString& aStr) { dwContentType().SetTypeStr(DwString(aStr)); dwContentType().Parse(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setType(int aType) { dwContentType().SetType(aType); dwContentType().Assemble(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- TQCString KMMessage::subtypeStr() const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().SubtypeStr().c_str(); else return ""; } //----------------------------------------------------------------------------- int KMMessage::subtype() const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().Subtype(); else return DwMime::kSubtypeNull; } //----------------------------------------------------------------------------- void KMMessage::setSubtypeStr(const TQCString& aStr) { dwContentType().SetSubtypeStr(DwString(aStr)); dwContentType().Parse(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setSubtype(int aSubtype) { dwContentType().SetSubtype(aSubtype); dwContentType().Assemble(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setDwMediaTypeParam( DwMediaType &mType, const TQCString& attr, const TQCString& val ) { mType.Parse(); DwParameter *param = mType.FirstParameter(); while(param) { if (!kasciistricmp(param->Attribute().c_str(), attr)) break; else param = param->Next(); } if (!param){ param = new DwParameter; param->SetAttribute(DwString( attr )); mType.AddParameter( param ); } else mType.SetModified(); param->SetValue(DwString( val )); mType.Assemble(); } //----------------------------------------------------------------------------- void KMMessage::setContentTypeParam(const TQCString& attr, const TQCString& val) { if (mNeedsAssembly) mMsg->Assemble(); mNeedsAssembly = false; setDwMediaTypeParam( dwContentType(), attr, val ); mNeedsAssembly = true; } //----------------------------------------------------------------------------- TQCString KMMessage::contentTransferEncodingStr() const { DwHeaders& header = mMsg->Headers(); if (header.HasContentTransferEncoding()) return header.ContentTransferEncoding().AsString().c_str(); else return ""; } //----------------------------------------------------------------------------- int KMMessage::contentTransferEncoding( DwEntity *entity ) const { if ( !entity ) entity = mMsg; DwHeaders& header = entity->Headers(); if ( header.HasContentTransferEncoding() ) return header.ContentTransferEncoding().AsEnum(); else return DwMime::kCteNull; } //----------------------------------------------------------------------------- void KMMessage::setContentTransferEncodingStr( const TQCString& cteString, DwEntity *entity ) { if ( !entity ) entity = mMsg; entity->Headers().ContentTransferEncoding().FromString( cteString ); entity->Headers().ContentTransferEncoding().Parse(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setContentTransferEncoding( int cte, DwEntity *entity ) { if ( !entity ) entity = mMsg; entity->Headers().ContentTransferEncoding().FromEnum( cte ); mNeedsAssembly = true; } //----------------------------------------------------------------------------- DwHeaders& KMMessage::headers() const { return mMsg->Headers(); } //----------------------------------------------------------------------------- void KMMessage::setNeedsAssembly() { mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::assembleIfNeeded() { Q_ASSERT( mMsg ); if ( mNeedsAssembly ) { mMsg->Assemble(); mNeedsAssembly = false; } } //----------------------------------------------------------------------------- TQCString KMMessage::body() const { const DwString& body = mMsg->Body().AsString(); TQCString str = KMail::Util::CString( body ); // Calls length() -> slow //kdWarning( str.length() != body.length(), 5006 ) // << "KMMessage::body(): body is binary but used as text!" << endl; return str; } //----------------------------------------------------------------------------- TQByteArray KMMessage::bodyDecodedBinary() const { DwString dwstr; const DwString& dwsrc = mMsg->Body().AsString(); switch (cte()) { case DwMime::kCteBase64: DwDecodeBase64(dwsrc, dwstr); break; case DwMime::kCteQuotedPrintable: DwDecodeQuotedPrintable(dwsrc, dwstr); break; default: dwstr = dwsrc; break; } int len = dwstr.size(); TQByteArray ba(len); memcpy(ba.data(),dwstr.data(),len); return ba; } //----------------------------------------------------------------------------- TQCString KMMessage::bodyDecoded() const { DwString dwstr; DwString dwsrc = mMsg->Body().AsString(); switch (cte()) { case DwMime::kCteBase64: DwDecodeBase64(dwsrc, dwstr); break; case DwMime::kCteQuotedPrintable: DwDecodeQuotedPrintable(dwsrc, dwstr); break; default: dwstr = dwsrc; break; } return KMail::Util::CString( dwstr ); // Calling TQCString::length() is slow //TQCString result = KMail::Util::CString( dwstr ); //kdWarning(result.length() != len, 5006) // << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl; //return result; } //----------------------------------------------------------------------------- TQValueList KMMessage::determineAllowedCtes( const CharFreq& cf, bool allow8Bit, bool willBeSigned ) { TQValueList allowedCtes; switch ( cf.type() ) { case CharFreq::SevenBitText: allowedCtes << DwMime::kCte7bit; case CharFreq::EightBitText: if ( allow8Bit ) allowedCtes << DwMime::kCte8bit; case CharFreq::SevenBitData: if ( cf.printableRatio() > 5.0/6.0 ) { // let n the length of data and p the number of printable chars. // Then base64 \approx 4n/3; qp \approx p + 3(n-p) // => qp < base64 iff p > 5n/6. allowedCtes << DwMime::kCteQp; allowedCtes << DwMime::kCteBase64; } else { allowedCtes << DwMime::kCteBase64; allowedCtes << DwMime::kCteQp; } break; case CharFreq::EightBitData: allowedCtes << DwMime::kCteBase64; break; case CharFreq::None: default: // just nothing (avoid compiler warning) ; } // In the following cases only QP and Base64 are allowed: // - the buffer will be OpenPGP/MIME signed and it contains trailing // whitespace (cf. RFC 3156) // - a line starts with "From " if ( ( willBeSigned && cf.hasTrailingWhitespace() ) || cf.hasLeadingFrom() ) { allowedCtes.remove( DwMime::kCte8bit ); allowedCtes.remove( DwMime::kCte7bit ); } return allowedCtes; } //----------------------------------------------------------------------------- void KMMessage::setBodyAndGuessCte( const TQByteArray& aBuf, TQValueList & allowedCte, bool allow8Bit, bool willBeSigned, DwEntity *entity ) { if ( !entity ) entity = mMsg; CharFreq cf( aBuf ); // it's safe to pass null arrays allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned ); setCte( allowedCte[0], entity ); // choose best fitting setBodyEncodedBinary( aBuf, entity ); } //----------------------------------------------------------------------------- void KMMessage::setBodyAndGuessCte( const TQCString& aBuf, TQValueList & allowedCte, bool allow8Bit, bool willBeSigned, DwEntity *entity ) { if ( !entity ) entity = mMsg; CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned ); setCte( allowedCte[0], entity ); // choose best fitting setBodyEncoded( aBuf, entity ); } //----------------------------------------------------------------------------- void KMMessage::setBodyEncoded(const TQCString& aStr, DwEntity *entity ) { if ( !entity ) entity = mMsg; DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */); DwString dwResult; switch (cte( entity )) { case DwMime::kCteBase64: DwEncodeBase64(dwSrc, dwResult); break; case DwMime::kCteQuotedPrintable: DwEncodeQuotedPrintable(dwSrc, dwResult); break; default: dwResult = dwSrc; break; } entity->Body().FromString(dwResult); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setBodyEncodedBinary( const TQByteArray& aStr, DwEntity *entity ) { if ( !entity ) entity = mMsg; DwString dwSrc(aStr.data(), aStr.size()); DwString dwResult; switch ( cte( entity ) ) { case DwMime::kCteBase64: DwEncodeBase64( dwSrc, dwResult ); break; case DwMime::kCteQuotedPrintable: DwEncodeQuotedPrintable( dwSrc, dwResult ); break; default: dwResult = dwSrc; break; } entity->Body().FromString( dwResult ); entity->Body().Parse(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setBody(const TQCString& aStr) { mMsg->Body().FromString(KMail::Util::dwString(aStr)); mNeedsAssembly = true; } void KMMessage::setBody(const DwString& aStr) { mMsg->Body().FromString(aStr); mNeedsAssembly = true; } void KMMessage::setBody(const char* aStr) { mMsg->Body().FromString(aStr); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setMultiPartBody( const TQCString & aStr ) { setBody( aStr ); mMsg->Body().Parse(); mNeedsAssembly = true; } // Patched by Daniel Moisset // modified numbodyparts, bodypart to take nested body parts as // a linear sequence. // third revision, Sep 26 2000 // this is support structure for traversing tree without recursion //----------------------------------------------------------------------------- int KMMessage::numBodyParts() const { int count = 0; DwBodyPart* part = getFirstDwBodyPart(); TQPtrList< DwBodyPart > parts; while (part) { //dive into multipart messages while ( part && part->hasHeaders() && part->Headers().HasContentType() && part->Body().FirstBodyPart() && (DwMime::kTypeMultipart == part->Headers().ContentType().Type()) ) { parts.append( part ); part = part->Body().FirstBodyPart(); } // this is where currPart->msgPart contains a leaf message part count++; // go up in the tree until reaching a node with next // (or the last top-level node) while (part && !(part->Next()) && !(parts.isEmpty())) { part = parts.getLast(); parts.removeLast(); } if (part && part->Body().Message() && part->Body().Message()->Body().FirstBodyPart()) { part = part->Body().Message()->Body().FirstBodyPart(); } else if (part) { part = part->Next(); } } return count; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::getFirstDwBodyPart() const { return mMsg->Body().FirstBodyPart(); } //----------------------------------------------------------------------------- int KMMessage::partNumber( DwBodyPart * aDwBodyPart ) const { DwBodyPart *curpart; TQPtrList< DwBodyPart > parts; int curIdx = 0; int idx = 0; // Get the DwBodyPart for this index curpart = getFirstDwBodyPart(); while (curpart && !idx) { //dive into multipart messages while( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && curpart->Body().FirstBodyPart() && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) { parts.append( curpart ); curpart = curpart->Body().FirstBodyPart(); } // this is where currPart->msgPart contains a leaf message part if (curpart == aDwBodyPart) idx = curIdx; curIdx++; // go up in the tree until reaching a node with next // (or the last top-level node) while (curpart && !(curpart->Next()) && !(parts.isEmpty())) { curpart = parts.getLast(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } return idx; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const { DwBodyPart *part, *curpart; TQPtrList< DwBodyPart > parts; int curIdx = 0; // Get the DwBodyPart for this index curpart = getFirstDwBodyPart(); part = 0; while (curpart && !part) { //dive into multipart messages while( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && curpart->Body().FirstBodyPart() && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) { parts.append( curpart ); curpart = curpart->Body().FirstBodyPart(); } // this is where currPart->msgPart contains a leaf message part if (curIdx==aIdx) part = curpart; curIdx++; // go up in the tree until reaching a node with next // (or the last top-level node) while (curpart && !(curpart->Next()) && !(parts.isEmpty())) { curpart = parts.getLast(); parts.removeLast(); } if (curpart) curpart = curpart->Next(); } return part; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const { DwBodyPart *part, *curpart; TQPtrList< DwBodyPart > parts; // Get the DwBodyPart for this index curpart = getFirstDwBodyPart(); part = 0; while (curpart && !part) { //dive into multipart messages while(curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && curpart->Body().FirstBodyPart() && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) { parts.append( curpart ); curpart = curpart->Body().FirstBodyPart(); } // this is where curPart->msgPart contains a leaf message part // pending(khz): Find out WHY this look does not travel down *into* an // embedded "Message/RfF822" message containing a "Multipart/Mixed" if ( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) { kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str() << " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl; } if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && curpart->Headers().ContentType().Type() == type && curpart->Headers().ContentType().Subtype() == subtype) { part = curpart; } else { // go up in the tree until reaching a node with next // (or the last top-level node) while (curpart && !(curpart->Next()) && !(parts.isEmpty())) { curpart = parts.getLast(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } } return part; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::findDwBodyPart( const TQCString& type, const TQCString& subtype ) const { DwBodyPart *part, *curpart; TQPtrList< DwBodyPart > parts; // Get the DwBodyPart for this index curpart = getFirstDwBodyPart(); part = 0; while (curpart && !part) { //dive into multipart messages while(curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && curpart->Body().FirstBodyPart() && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) { parts.append( curpart ); curpart = curpart->Body().FirstBodyPart(); } // this is where curPart->msgPart contains a leaf message part // pending(khz): Find out WHY this look does not travel down *into* an // embedded "Message/RfF822" message containing a "Multipart/Mixed" if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) { kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str() << " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl; } if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && curpart->Headers().ContentType().TypeStr().c_str() == type && curpart->Headers().ContentType().SubtypeStr().c_str() == subtype) { part = curpart; } else { // go up in the tree until reaching a node with next // (or the last top-level node) while (curpart && !(curpart->Next()) && !(parts.isEmpty())) { curpart = parts.getLast(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } } return part; } void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ) { // TODO: Instead of manually implementing RFC2231 header encoding (i.e. // possibly multiple values given as paramname*0=..; parmaname*1=..;... // or par as paramname*0*=..; parmaname*1*=..;..., which should be // concatenated), use a generic method to decode the header, using RFC // 2047 or 2231, or whatever future RFC might be appropriate! // Right now, some fields are decoded, while others are not. E.g. // Content-Disposition is not decoded here, rather only on demand in // KMMsgPart::fileName; Name however is decoded here and stored as a // decoded String in KMMsgPart... // Content-type TQCString additionalCTypeParams; if (headers.HasContentType()) { DwMediaType& ct = headers.ContentType(); aPart->setOriginalContentTypeStr( ct.AsString().c_str() ); aPart->setTypeStr(ct.TypeStr().c_str()); aPart->setSubtypeStr(ct.SubtypeStr().c_str()); DwParameter *param = ct.FirstParameter(); while(param) { if (!tqstricmp(param->Attribute().c_str(), "charset")) { if (aPart->type() == DwMime::kTypeText) { aPart->setCharset(TQCString(param->Value().c_str()).lower()); } } else if (!tqstrnicmp(param->Attribute().c_str(), "name*", 5)) aPart->setName(KMMsgBase::decodeRFC2231String(KMMsgBase::extractRFC2231HeaderField( param->Value().c_str(), "name" ))); else { additionalCTypeParams += ';'; additionalCTypeParams += param->AsString().c_str(); } param=param->Next(); } } else { aPart->setTypeStr("text"); // Set to defaults aPart->setSubtypeStr("plain"); } aPart->setAdditionalCTypeParamStr( additionalCTypeParams ); // Modification by Markus if (aPart->name().isEmpty()) { if (headers.HasContentType() && !headers.ContentType().Name().empty()) { aPart->setName(KMMsgBase::decodeRFC2047String(headers. ContentType().Name().c_str()) ); } else if (headers.HasSubject() && !headers.Subject().AsString().empty()) { aPart->setName( KMMsgBase::decodeRFC2047String(headers. Subject().AsString().c_str()) ); } } // Content-transfer-encoding if (headers.HasContentTransferEncoding()) aPart->setCteStr(headers.ContentTransferEncoding().AsString().c_str()); else aPart->setCteStr("7bit"); // Content-description if (headers.HasContentDescription()) aPart->setContentDescription( KMMsgBase::decodeRFC2047String( headers.ContentDescription().AsString().c_str() ) ); else aPart->setContentDescription(""); // Content-disposition if (headers.HasContentDisposition()) aPart->setContentDisposition(headers.ContentDisposition().AsString().c_str()); else aPart->setContentDisposition(""); } //----------------------------------------------------------------------------- void KMMessage::bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart, bool withBody) { if ( !aPart ) return; aPart->clear(); if( aDwBodyPart && aDwBodyPart->hasHeaders() ) { // This must not be an empty string, because we'll get a // spurious empty Subject: line in some of the parts. //aPart->setName(" "); // partSpecifier TQString partId( aDwBodyPart->partId() ); aPart->setPartSpecifier( partId ); DwHeaders& headers = aDwBodyPart->Headers(); applyHeadersToMessagePart( headers, aPart ); // Body if (withBody) aPart->setBody( aDwBodyPart->Body().AsString() ); else aPart->setBody( TQCString("") ); // Content-id if ( headers.HasContentId() ) { const TQCString contentId = headers.ContentId().AsString().c_str(); // ignore leading '<' and trailing '>' aPart->setContentId( contentId.mid( 1, contentId.length() - 2 ) ); } } // If no valid body part was given, // set all MultipartBodyPart attributes to empty values. else { aPart->setTypeStr(""); aPart->setSubtypeStr(""); aPart->setCteStr(""); // This must not be an empty string, because we'll get a // spurious empty Subject: line in some of the parts. //aPart->setName(" "); aPart->setContentDescription(""); aPart->setContentDisposition(""); aPart->setBody(TQCString("")); aPart->setContentId(""); } } //----------------------------------------------------------------------------- void KMMessage::bodyPart(int aIdx, KMMessagePart* aPart) const { if ( !aPart ) return; // If the DwBodyPart was found get the header fields and body if ( DwBodyPart *part = dwBodyPart( aIdx ) ) { KMMessage::bodyPart(part, aPart); if( aPart->name().isEmpty() ) aPart->setName( i18n("Attachment: %1").arg( aIdx ) ); } } //----------------------------------------------------------------------------- void KMMessage::deleteBodyParts() { mMsg->Body().DeleteBodyParts(); } //----------------------------------------------------------------------------- bool KMMessage::deleteBodyPart( int partIndex ) { KMMessagePart part; DwBodyPart *dwpart = findPart( partIndex ); if ( !dwpart ) return false; KMMessage::bodyPart( dwpart, &part, true ); if ( !part.isComplete() ) return false; DwBody *parentNode = dynamic_cast( dwpart->Parent() ); if ( !parentNode ) return false; parentNode->RemoveBodyPart( dwpart ); // add dummy part to show that a attachment has been deleted KMMessagePart dummyPart; dummyPart.duplicate( part ); TQString comment = i18n("This attachment has been deleted."); if ( !part.fileName().isEmpty() ) comment = i18n( "The attachment '%1' has been deleted." ).arg( part.fileName() ); dummyPart.setContentDescription( comment ); dummyPart.setBodyEncodedBinary( TQByteArray() ); TQCString cd = dummyPart.contentDisposition(); if ( cd.find( "inline", 0, false ) == 0 ) { cd.replace( 0, 10, "attachment" ); dummyPart.setContentDisposition( cd ); } else if ( cd.isEmpty() ) { dummyPart.setContentDisposition( "attachment" ); } DwBodyPart* newDwPart = createDWBodyPart( &dummyPart ); parentNode->AddBodyPart( newDwPart ); getTopLevelPart()->Assemble(); return true; } //----------------------------------------------------------------------------- DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) { DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0); if ( !aPart ) return part; TQCString charset = aPart->charset(); TQCString type = aPart->typeStr(); TQCString subtype = aPart->subtypeStr(); TQCString cte = aPart->cteStr(); TQCString contDesc = aPart->contentDescriptionEncoded(); TQCString contDisp = aPart->contentDisposition(); TQCString name = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->name(), charset ); bool RFC2231encoded = aPart->name() != TQString(name); TQCString paramAttr = aPart->parameterAttribute(); DwHeaders& headers = part->Headers(); DwMediaType& ct = headers.ContentType(); if (!type.isEmpty() && !subtype.isEmpty()) { ct.SetTypeStr(type.data()); ct.SetSubtypeStr(subtype.data()); if (!charset.isEmpty()){ DwParameter *param; param=new DwParameter; param->SetAttribute("charset"); param->SetValue(charset.data()); ct.AddParameter(param); } } TQCString additionalParam = aPart->additionalCTypeParamStr(); if( !additionalParam.isEmpty() ) { TQCString parAV; DwString parA, parV; int iL, i1, i2, iM; iL = additionalParam.length(); i1 = 0; i2 = additionalParam.find(';', i1, false); while ( i1 < iL ) { if( -1 == i2 ) i2 = iL; if( i1+1 < i2 ) { parAV = additionalParam.mid( i1, (i2-i1) ); iM = parAV.find('='); if( -1 < iM ) { parA = parAV.left( iM ).data(); parV = parAV.right( parAV.length() - iM - 1 ).data(); if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) ) { parV.erase( 0, 1); parV.erase( parV.length()-1 ); } } else { parA = parAV.data(); parV = ""; } DwParameter *param; param = new DwParameter; param->SetAttribute( parA ); param->SetValue( parV ); ct.AddParameter( param ); } i1 = i2+1; i2 = additionalParam.find(';', i1, false); } } if ( !name.isEmpty() ) { if (RFC2231encoded) { DwParameter *nameParam; nameParam = new DwParameter; nameParam->SetAttribute("name*"); nameParam->SetValue(name.data(),true); ct.AddParameter(nameParam); } else { ct.SetName(name.data()); } } if (!paramAttr.isEmpty()) { TQCString paramValue; paramValue = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->parameterValue(), charset ); DwParameter *param = new DwParameter; if (aPart->parameterValue() != TQString(paramValue)) { param->SetAttribute((paramAttr + '*').data()); param->SetValue(paramValue.data(),true); } else { param->SetAttribute(paramAttr.data()); param->SetValue(paramValue.data()); } ct.AddParameter(param); } if (!cte.isEmpty()) headers.Cte().FromString(cte); if (!contDesc.isEmpty()) headers.ContentDescription().FromString(contDesc); if (!contDisp.isEmpty()) headers.ContentDisposition().FromString(contDisp); const DwString bodyStr = aPart->dwBody(); if (!bodyStr.empty()) part->Body().FromString(bodyStr); else part->Body().FromString(""); if (!aPart->partSpecifier().isNull()) part->SetPartId( aPart->partSpecifier().latin1() ); if (aPart->decodedSize() > 0) part->SetBodySize( aPart->decodedSize() ); return part; } //----------------------------------------------------------------------------- void KMMessage::addDwBodyPart(DwBodyPart * aDwPart) { mMsg->Body().AddBodyPart( aDwPart ); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::addBodyPart(const KMMessagePart* aPart) { DwBodyPart* part = createDWBodyPart( aPart ); addDwBodyPart( part ); } //----------------------------------------------------------------------------- TQString KMMessage::generateMessageId( const TQString& addr ) { TQDateTime datetime = TQDateTime::currentDateTime(); TQString msgIdStr; msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" ); TQString msgIdSuffix; TDEConfigGroup general( KMKernel::config(), "General" ); if( general.readBoolEntry( "useCustomMessageIdSuffix", false ) ) msgIdSuffix = general.readEntry( "myMessageIdSuffix" ); if( !msgIdSuffix.isEmpty() ) msgIdStr += '@' + msgIdSuffix; else msgIdStr += '.' + KPIM::encodeIDN( addr ); msgIdStr += '>'; return msgIdStr; } //----------------------------------------------------------------------------- TQCString KMMessage::html2source( const TQCString & src ) { TQCString result( 1 + 6*(src.size()-1) ); // maximal possible length TQCString::ConstIterator s = src.begin(); TQCString::Iterator d = result.begin(); while ( *s ) { switch ( *s ) { case '<': { *d++ = '&'; *d++ = 'l'; *d++ = 't'; *d++ = ';'; ++s; } break; case '\r': { ++s; } break; case '\n': { *d++ = '<'; *d++ = 'b'; *d++ = 'r'; *d++ = '>'; ++s; } break; case '>': { *d++ = '&'; *d++ = 'g'; *d++ = 't'; *d++ = ';'; ++s; } break; case '&': { *d++ = '&'; *d++ = 'a'; *d++ = 'm'; *d++ = 'p'; *d++ = ';'; ++s; } break; case '"': { *d++ = '&'; *d++ = 'q'; *d++ = 'u'; *d++ = 'o'; *d++ = 't'; *d++ = ';'; ++s; } break; case '\'': { *d++ = '&'; *d++ = 'a'; *d++ = 'p'; *d++ = 's'; *d++ = ';'; ++s; } break; default: *d++ = *s++; } } result.truncate( d - result.begin() ); // adds trailing NUL return result; } //----------------------------------------------------------------------------- TQString KMMessage::encodeMailtoUrl( const TQString& str ) { TQString result; result = TQString::fromLatin1( KMMsgBase::encodeRFC2047String( str, "utf-8" ) ); result = KURL::encode_string( result ); return result; } //----------------------------------------------------------------------------- TQString KMMessage::decodeMailtoUrl( const TQString& url ) { TQString result; result = KURL::decode_string( url ); result = KMMsgBase::decodeRFC2047String( result.latin1() ); return result; } //----------------------------------------------------------------------------- TQCString KMMessage::stripEmailAddr( const TQCString& aStr ) { //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl; if ( aStr.isEmpty() ) return TQCString(); TQCString result; // The following is a primitive parser for a mailbox-list (cf. RFC 2822). // The purpose is to extract a displayable string from the mailboxes. // Comments in the addr-spec are not handled. No error checking is done. TQCString name; TQCString comment; TQCString angleAddress; enum { TopLevel, InComment, InAngleAddress } context = TopLevel; bool inQuotedString = false; int commentLevel = 0; for ( const char* p = aStr.data(); *p; ++p ) { switch ( context ) { case TopLevel : { switch ( *p ) { case '"' : inQuotedString = !inQuotedString; break; case '(' : if ( !inQuotedString ) { context = InComment; commentLevel = 1; } else name += *p; break; case '<' : if ( !inQuotedString ) { context = InAngleAddress; } else name += *p; break; case '\\' : // quoted character ++p; // skip the '\' if ( *p ) name += *p; break; case ',' : if ( !inQuotedString ) { // next email address if ( !result.isEmpty() ) result += ", "; name = name.stripWhiteSpace(); comment = comment.stripWhiteSpace(); angleAddress = angleAddress.stripWhiteSpace(); /* kdDebug(5006) << "Name : \"" << name << "\"" << endl; kdDebug(5006) << "Comment : \"" << comment << "\"" << endl; kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl; */ if ( angleAddress.isEmpty() && !comment.isEmpty() ) { // handle Outlook-style addresses like // john.doe@invalid (John Doe) result += comment; } else if ( !name.isEmpty() ) { result += name; } else if ( !comment.isEmpty() ) { result += comment; } else if ( !angleAddress.isEmpty() ) { result += angleAddress; } name = TQCString(); comment = TQCString(); angleAddress = TQCString(); } else name += *p; break; default : name += *p; } break; } case InComment : { switch ( *p ) { case '(' : ++commentLevel; comment += *p; break; case ')' : --commentLevel; if ( commentLevel == 0 ) { context = TopLevel; comment += ' '; // separate the text of several comments } else comment += *p; break; case '\\' : // quoted character ++p; // skip the '\' if ( *p ) comment += *p; break; default : comment += *p; } break; } case InAngleAddress : { switch ( *p ) { case '"' : inQuotedString = !inQuotedString; angleAddress += *p; break; case '>' : if ( !inQuotedString ) { context = TopLevel; } else angleAddress += *p; break; case '\\' : // quoted character ++p; // skip the '\' if ( *p ) angleAddress += *p; break; default : angleAddress += *p; } break; } } // switch ( context ) } if ( !result.isEmpty() ) result += ", "; name = name.stripWhiteSpace(); comment = comment.stripWhiteSpace(); angleAddress = angleAddress.stripWhiteSpace(); /* kdDebug(5006) << "Name : \"" << name << "\"" << endl; kdDebug(5006) << "Comment : \"" << comment << "\"" << endl; kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl; */ if ( angleAddress.isEmpty() && !comment.isEmpty() ) { // handle Outlook-style addresses like // john.doe@invalid (John Doe) result += comment; } else if ( !name.isEmpty() ) { result += name; } else if ( !comment.isEmpty() ) { result += comment; } else if ( !angleAddress.isEmpty() ) { result += angleAddress; } //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result // << "\"" << endl; return result; } //----------------------------------------------------------------------------- TQString KMMessage::stripEmailAddr( const TQString& aStr ) { //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl; if ( aStr.isEmpty() ) return TQString(); TQString result; // The following is a primitive parser for a mailbox-list (cf. RFC 2822). // The purpose is to extract a displayable string from the mailboxes. // Comments in the addr-spec are not handled. No error checking is done. TQString name; TQString comment; TQString angleAddress; enum { TopLevel, InComment, InAngleAddress } context = TopLevel; bool inQuotedString = false; int commentLevel = 0; TQChar ch; unsigned int strLength(aStr.length()); for ( uint index = 0; index < strLength; ++index ) { ch = aStr[index]; switch ( context ) { case TopLevel : { switch ( ch.latin1() ) { case '"' : inQuotedString = !inQuotedString; break; case '(' : if ( !inQuotedString ) { context = InComment; commentLevel = 1; } else name += ch; break; case '<' : if ( !inQuotedString ) { context = InAngleAddress; } else name += ch; break; case '\\' : // quoted character ++index; // skip the '\' if ( index < aStr.length() ) name += aStr[index]; break; case ',' : if ( !inQuotedString ) { // next email address if ( !result.isEmpty() ) result += ", "; name = name.stripWhiteSpace(); comment = comment.stripWhiteSpace(); angleAddress = angleAddress.stripWhiteSpace(); /* kdDebug(5006) << "Name : \"" << name << "\"" << endl; kdDebug(5006) << "Comment : \"" << comment << "\"" << endl; kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl; */ if ( angleAddress.isEmpty() && !comment.isEmpty() ) { // handle Outlook-style addresses like // john.doe@invalid (John Doe) result += comment; } else if ( !name.isEmpty() ) { result += name; } else if ( !comment.isEmpty() ) { result += comment; } else if ( !angleAddress.isEmpty() ) { result += angleAddress; } name = TQString(); comment = TQString(); angleAddress = TQString(); } else name += ch; break; default : name += ch; } break; } case InComment : { switch ( ch.latin1() ) { case '(' : ++commentLevel; comment += ch; break; case ')' : --commentLevel; if ( commentLevel == 0 ) { context = TopLevel; comment += ' '; // separate the text of several comments } else comment += ch; break; case '\\' : // quoted character ++index; // skip the '\' if ( index < aStr.length() ) comment += aStr[index]; break; default : comment += ch; } break; } case InAngleAddress : { switch ( ch.latin1() ) { case '"' : inQuotedString = !inQuotedString; angleAddress += ch; break; case '>' : if ( !inQuotedString ) { context = TopLevel; } else angleAddress += ch; break; case '\\' : // quoted character ++index; // skip the '\' if ( index < aStr.length() ) angleAddress += aStr[index]; break; default : angleAddress += ch; } break; } } // switch ( context ) } if ( !result.isEmpty() ) result += ", "; name = name.stripWhiteSpace(); comment = comment.stripWhiteSpace(); angleAddress = angleAddress.stripWhiteSpace(); /* kdDebug(5006) << "Name : \"" << name << "\"" << endl; kdDebug(5006) << "Comment : \"" << comment << "\"" << endl; kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl; */ if ( angleAddress.isEmpty() && !comment.isEmpty() ) { // handle Outlook-style addresses like // john.doe@invalid (John Doe) result += comment; } else if ( !name.isEmpty() ) { result += name; } else if ( !comment.isEmpty() ) { result += comment; } else if ( !angleAddress.isEmpty() ) { result += angleAddress; } //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result // << "\"" << endl; return result; } //----------------------------------------------------------------------------- TQString KMMessage::quoteHtmlChars( const TQString& str, bool removeLineBreaks ) { TQString result; unsigned int strLength(str.length()); result.reserve( 6*strLength ); // maximal possible length for( unsigned int i = 0; i < strLength; ++i ) switch ( str[i].latin1() ) { case '<': result += "<"; break; case '>': result += ">"; break; case '&': result += "&"; break; case '"': result += """; break; case '\n': if ( !removeLineBreaks ) result += "
"; break; case '\r': // ignore CR break; default: result += str[i]; } result.squeeze(); return result; } //----------------------------------------------------------------------------- TQString KMMessage::emailAddrAsAnchor(const TQString& aEmail, bool stripped, const TQString& cssStyle, bool aLink) { if( aEmail.isEmpty() ) return aEmail; TQStringList addressList = KPIM::splitEmailAddrList( aEmail ); TQString result; for( TQStringList::ConstIterator it = addressList.begin(); ( it != addressList.end() ); ++it ) { if( !(*it).isEmpty() ) { // Extract the name, mail and some pretty versions out of the mail address TQString name, mail; KPIM::getNameAndMail( *it, name, mail ); TQString pretty; TQString prettyStripped; if ( name.stripWhiteSpace().isEmpty() ) { pretty = mail; prettyStripped = mail; } else { pretty = KPIM::quoteNameIfNecessary( name ) + " <" + mail + ">"; prettyStripped = name; } if(aLink) { result += ""; } if ( stripped ) { result += KMMessage::quoteHtmlChars( prettyStripped, true ); } else { result += KMMessage::quoteHtmlChars( pretty, true ); } if(aLink) result += ", "; } } // cut of the trailing ", " if(aLink) result.truncate( result.length() - 2 ); //kdDebug(5006) << "KMMessage::emailAddrAsAnchor('" << aEmail // << "') returns:\n-->" << result << "<--" << endl; return result; } //----------------------------------------------------------------------------- //static TQStringList KMMessage::stripAddressFromAddressList( const TQString& address, const TQStringList& list ) { TQStringList addresses( list ); TQString addrSpec( KPIM::getEmailAddress( address ) ); for ( TQStringList::Iterator it = addresses.begin(); it != addresses.end(); ) { if ( kasciistricmp( addrSpec.utf8().data(), KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) { kdDebug(5006) << "Removing " << *it << " from the address list" << endl; it = addresses.remove( it ); } else ++it; } return addresses; } //----------------------------------------------------------------------------- //static TQStringList KMMessage::stripMyAddressesFromAddressList( const TQStringList& list ) { TQStringList addresses = list; for( TQStringList::Iterator it = addresses.begin(); it != addresses.end(); ) { kdDebug(5006) << "Check whether " << *it << " is one of my addresses" << endl; if( kmkernel->identityManager()->thatIsMe( KPIM::getEmailAddress( *it ) ) ) { kdDebug(5006) << "Removing " << *it << " from the address list" << endl; it = addresses.remove( it ); } else ++it; } return addresses; } //----------------------------------------------------------------------------- //static bool KMMessage::addressIsInAddressList( const TQString& address, const TQStringList& addresses ) { TQString addrSpec = KPIM::getEmailAddress( address ); for( TQStringList::ConstIterator it = addresses.begin(); it != addresses.end(); ++it ) { if ( kasciistricmp( addrSpec.utf8().data(), KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) return true; } return false; } //----------------------------------------------------------------------------- //static TQString KMMessage::expandAliases( const TQString& recipients ) { if ( recipients.isEmpty() ) return TQString(); TQStringList recipientList = KPIM::splitEmailAddrList( recipients ); TQString expandedRecipients; for ( TQStringList::Iterator it = recipientList.begin(); it != recipientList.end(); ++it ) { if ( !expandedRecipients.isEmpty() ) expandedRecipients += ", "; TQString receiver = (*it).stripWhiteSpace(); // try to expand distribution list TQString expandedList = KAddrBookExternal::expandDistributionList( receiver ); if ( !expandedList.isEmpty() ) { expandedRecipients += expandedList; continue; } // try to expand nick name TQString expandedNickName = KabcBridge::expandNickName( receiver ); if ( !expandedNickName.isEmpty() ) { expandedRecipients += expandedNickName; continue; } // check whether the address is missing the domain part // FIXME: looking for '@' might be wrong if ( receiver.find('@') == -1 ) { TDEConfigGroup general( KMKernel::config(), "General" ); TQString defaultdomain = general.readEntry( "Default domain" ); if( !defaultdomain.isEmpty() ) { expandedRecipients += receiver + "@" + defaultdomain; } else { expandedRecipients += guessEmailAddressFromLoginName( receiver ); } } else expandedRecipients += receiver; } return expandedRecipients; } //----------------------------------------------------------------------------- //static TQString KMMessage::guessEmailAddressFromLoginName( const TQString& loginName ) { if ( loginName.isEmpty() ) return TQString(); char hostnameC[256]; // null terminate this C string hostnameC[255] = '\0'; // set the string to 0 length if gethostname fails if ( gethostname( hostnameC, 255 ) ) hostnameC[0] = '\0'; TQString address = loginName; address += '@'; address += TQString::fromLocal8Bit( hostnameC ); // try to determine the real name const KUser user( loginName ); if ( user.isValid() ) { TQString fullName = user.fullName(); if ( fullName.find( TQRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) != -1 ) address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" ) + "\" <" + address + '>'; else address = fullName + " <" + address + '>'; } return address; } //----------------------------------------------------------------------------- void KMMessage::readConfig() { KMMsgBase::readConfig(); TDEConfig *config=KMKernel::config(); TDEConfigGroupSaver saver(config, "General"); config->setGroup("General"); int languageNr = config->readNumEntry("reply-current-language",0); { // area for config group "KMMessage #n" TDEConfigGroupSaver saver(config, TQString("KMMessage #%1").arg(languageNr)); sReplyLanguage = config->readEntry("language",TDEGlobal::locale()->language()); sReplyStr = config->readEntry("phrase-reply", i18n("On %D, you wrote:")); sReplyAllStr = config->readEntry("phrase-reply-all", i18n("On %D, %F wrote:")); sForwardStr = config->readEntry("phrase-forward", i18n("Forwarded Message")); sIndentPrefixStr = config->readEntry("indent-prefix",">%_"); } { // area for config group "Composer" TDEConfigGroupSaver saver(config, "Composer"); sSmartQuote = GlobalSettings::self()->smartQuote(); sWordWrap = GlobalSettings::self()->wordWrap(); sWrapCol = GlobalSettings::self()->lineWrapWidth(); if ((sWrapCol == 0) || (sWrapCol > 78)) sWrapCol = 78; if (sWrapCol < 30) sWrapCol = 30; sPrefCharsets = config->readListEntry("pref-charsets"); } { // area for config group "Reader" TDEConfigGroupSaver saver(config, "Reader"); sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) ); } } TQCString KMMessage::defaultCharset() { TQCString retval; if (!sPrefCharsets.isEmpty()) retval = sPrefCharsets[0].latin1(); if (retval.isEmpty() || (retval == "locale")) { retval = TQCString(kmkernel->networkCodec()->mimeName()); kasciitolower( retval.data() ); } if (retval == "jisx0208.1983-0") retval = "iso-2022-jp"; else if (retval == "ksc5601.1987-0") retval = "euc-kr"; return retval; } const TQStringList &KMMessage::preferredCharsets() { return sPrefCharsets; } //----------------------------------------------------------------------------- TQCString KMMessage::charset() const { if ( mMsg->Headers().HasContentType() ) { DwMediaType &mType=mMsg->Headers().ContentType(); mType.Parse(); DwParameter *param=mType.FirstParameter(); while(param){ if (!kasciistricmp(param->Attribute().c_str(), "charset")) return param->Value().c_str(); else param=param->Next(); } } return ""; // us-ascii, but we don't have to specify it } //----------------------------------------------------------------------------- void KMMessage::setCharset( const TQCString &charset, DwEntity *entity ) { kdWarning( type() != DwMime::kTypeText ) << "KMMessage::setCharset(): trying to set a charset for a non-textual mimetype." << endl << "Fix this caller:" << endl << "====================================================================" << endl << kdBacktrace( 5 ) << endl << "====================================================================" << endl; if ( !entity ) entity = mMsg; DwMediaType &mType = entity->Headers().ContentType(); mType.Parse(); DwParameter *param = mType.FirstParameter(); while( param ) { // FIXME use the mimelib functions here for comparison. if ( !kasciistricmp( param->Attribute().c_str(), "charset" ) ) break; param = param->Next(); } if ( !param ) { param = new DwParameter; param->SetAttribute( "charset" ); mType.AddParameter( param ); } else mType.SetModified(); TQCString lowerCharset = charset; kasciitolower( lowerCharset.data() ); param->SetValue( DwString( lowerCharset ) ); mType.Assemble(); } //----------------------------------------------------------------------------- void KMMessage::setStatus(const KMMsgStatus aStatus, int idx) { if (mStatus == aStatus) return; KMMsgBase::setStatus(aStatus, idx); } void KMMessage::setEncryptionState(const KMMsgEncryptionState s, int idx) { if( mEncryptionState == s ) return; mEncryptionState = s; mDirty = true; KMMsgBase::setEncryptionState(s, idx); } void KMMessage::setSignatureState(KMMsgSignatureState s, int idx) { if( mSignatureState == s ) return; mSignatureState = s; mDirty = true; KMMsgBase::setSignatureState(s, idx); } void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx ) { if ( mMDNSentState == status ) return; if ( status == 0 ) status = KMMsgMDNStateUnknown; mMDNSentState = status; mDirty = true; KMMsgBase::setMDNSentState( status, idx ); } //----------------------------------------------------------------------------- void KMMessage::link( const KMMessage *aMsg, KMMsgStatus aStatus ) { Q_ASSERT( aStatus == KMMsgStatusReplied || aStatus == KMMsgStatusForwarded || aStatus == KMMsgStatusDeleted ); TQString message = headerField( "X-KMail-Link-Message" ); if ( !message.isEmpty() ) message += ','; TQString type = headerField( "X-KMail-Link-Type" ); if ( !type.isEmpty() ) type += ','; message += TQString::number( aMsg->getMsgSerNum() ); if ( aStatus == KMMsgStatusReplied ) type += "reply"; else if ( aStatus == KMMsgStatusForwarded ) type += "forward"; else if ( aStatus == KMMsgStatusDeleted ) type += "deleted"; setHeaderField( "X-KMail-Link-Message", message ); setHeaderField( "X-KMail-Link-Type", type ); } //----------------------------------------------------------------------------- void KMMessage::getLink(int n, ulong *retMsgSerNum, KMMsgStatus *reStatus) const { *retMsgSerNum = 0; *reStatus = KMMsgStatusUnknown; TQString message = headerField("X-KMail-Link-Message"); TQString type = headerField("X-KMail-Link-Type"); message = message.section(',', n, n); type = type.section(',', n, n); if ( !message.isEmpty() && !type.isEmpty() ) { *retMsgSerNum = message.toULong(); if ( type == "reply" ) *reStatus = KMMsgStatusReplied; else if ( type == "forward" ) *reStatus = KMMsgStatusForwarded; else if ( type == "deleted" ) *reStatus = KMMsgStatusDeleted; } } //----------------------------------------------------------------------------- DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const TQString & partSpecifier ) { if ( !part ) return 0; DwBodyPart* current; if ( part->partId() == partSpecifier ) return part; // multipart if ( part->hasHeaders() && part->Headers().HasContentType() && part->Body().FirstBodyPart() && (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) && (current = findDwBodyPart( part->Body().FirstBodyPart(), partSpecifier )) ) { return current; } // encapsulated message if ( part->Body().Message() && part->Body().Message()->Body().FirstBodyPart() && (current = findDwBodyPart( part->Body().Message()->Body().FirstBodyPart(), partSpecifier )) ) { return current; } // next part return findDwBodyPart( part->Next(), partSpecifier ); } //----------------------------------------------------------------------------- void KMMessage::updateBodyPart(const TQString partSpecifier, const TQByteArray & data) { if ( !data.data() || !data.size() ) return; DwString content( data.data(), data.size() ); if ( numBodyParts() > 0 && partSpecifier != "0" && partSpecifier != "TEXT" ) { TQString specifier = partSpecifier; if ( partSpecifier.endsWith(".HEADER") || partSpecifier.endsWith(".MIME") ) { // get the parent bodypart specifier = partSpecifier.section( '.', 0, -2 ); } // search for the bodypart mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier ); kdDebug(5006) << "KMMessage::updateBodyPart " << specifier << endl; if (!mLastUpdated) { kdWarning(5006) << "KMMessage::updateBodyPart - can not find part " << specifier << endl; return; } if ( partSpecifier.endsWith(".MIME") ) { // update headers // get rid of EOL content.resize( TQMAX( content.length(), 2 ) - 2 ); // we have to delete the fields first as they might have been created by // an earlier call to DwHeaders::FieldBody mLastUpdated->Headers().DeleteAllFields(); mLastUpdated->Headers().FromString( content ); mLastUpdated->Headers().Parse(); } else if ( partSpecifier.endsWith(".HEADER") ) { // update header of embedded message mLastUpdated->Body().Message()->Headers().FromString( content ); mLastUpdated->Body().Message()->Headers().Parse(); } else { // update body mLastUpdated->Body().FromString( content ); TQString parentSpec = partSpecifier.section( '.', 0, -2 ); if ( !parentSpec.isEmpty() ) { DwBodyPart* parent = findDwBodyPart( getFirstDwBodyPart(), parentSpec ); if ( parent && parent->hasHeaders() && parent->Headers().HasContentType() ) { const DwMediaType& contentType = parent->Headers().ContentType(); if ( contentType.Type() == DwMime::kTypeMessage && contentType.Subtype() == DwMime::kSubtypeRfc822 ) { // an embedded message that is not multipart // update this directly parent->Body().Message()->Body().FromString( content ); } } } } } else { // update text-only messages if ( partSpecifier == "TEXT" ) deleteBodyParts(); // delete empty parts first mMsg->Body().FromString( content ); mMsg->Body().Parse(); } mNeedsAssembly = true; if (! partSpecifier.endsWith(".HEADER") ) { // notify observers notify(); } } void KMMessage::updateInvitationState() { if ( mMsg && mMsg->hasHeaders() && mMsg->Headers().HasContentType() ) { TQString cntType = mMsg->Headers().ContentType().TypeStr().c_str(); cntType += '/'; cntType += mMsg->Headers().ContentType().SubtypeStr().c_str(); if ( cntType.lower() == "text/calendar" ) { setStatus( KMMsgStatusHasInvitation ); return; } } setStatus( KMMsgStatusHasNoInvitation ); return; } //----------------------------------------------------------------------------- void KMMessage::updateAttachmentState( DwBodyPart* part ) { if ( !part ) part = getFirstDwBodyPart(); if ( !part ) { // kdDebug(5006) << "updateAttachmentState - no part!" << endl; setStatus( KMMsgStatusHasNoAttach ); return; } bool filenameEmpty = true; if ( part->hasHeaders() ) { if ( part->Headers().HasContentDisposition() ) { DwDispositionType cd = part->Headers().ContentDisposition(); filenameEmpty = cd.Filename().empty(); if ( filenameEmpty ) { // let's try if it is rfc 2231 encoded which mimelib can't handle filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( cd.AsString().c_str(), "filename" ) ).isEmpty(); } } // Filename still empty? Check if the content-type has a "name" parameter and try to use that as // the attachment name if ( filenameEmpty && part->Headers().HasContentType() ) { DwMediaType contentType = part->Headers().ContentType(); filenameEmpty = contentType.Name().empty(); if ( filenameEmpty ) { // let's try if it is rfc 2231 encoded which mimelib can't handle filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( contentType.AsString().c_str(), "name" ) ).isEmpty(); } } } if ( part->hasHeaders() && ( ( part->Headers().HasContentDisposition() && !part->Headers().ContentDisposition().Filename().empty() ) || ( part->Headers().HasContentType() && !filenameEmpty ) ) ) { // now blacklist certain ContentTypes if ( !part->Headers().HasContentType() || ( part->Headers().HasContentType() && part->Headers().ContentType().Subtype() != DwMime::kSubtypePgpSignature && part->Headers().ContentType().Subtype() != DwMime::kSubtypePkcs7Signature ) ) { setStatus( KMMsgStatusHasAttach ); } return; } // multipart if ( part->hasHeaders() && part->Headers().HasContentType() && part->Body().FirstBodyPart() && (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) ) { updateAttachmentState( part->Body().FirstBodyPart() ); } // encapsulated message if ( part->Body().Message() && part->Body().Message()->Body().FirstBodyPart() ) { updateAttachmentState( part->Body().Message()->Body().FirstBodyPart() ); } // next part if ( part->Next() ) updateAttachmentState( part->Next() ); else if ( attachmentState() == KMMsgAttachmentUnknown ) setStatus( KMMsgStatusHasNoAttach ); } void KMMessage::setBodyFromUnicode( const TQString &str, DwEntity *entity ) { TQCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str ); if ( encoding.isEmpty() ) encoding = "utf-8"; const TQTextCodec * codec = KMMsgBase::codecForName( encoding ); assert( codec ); TQValueList dummy; setCharset( encoding, entity ); setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */, false, entity ); } const TQTextCodec * KMMessage::codec() const { const TQTextCodec * c = mOverrideCodec; if ( !c ) // no override-codec set for this message, try the CT charset parameter: c = KMMsgBase::codecForName( charset() ); if ( !c ) { // Ok, no override and nothing in the message, let's use the fallback // the user configured c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() ); } if ( !c ) // no charset means us-ascii (RFC 2045), so using local encoding should // be okay c = kmkernel->networkCodec(); assert( c ); return c; } TQString KMMessage::bodyToUnicode(const TQTextCodec* codec) const { if ( !codec ) // No codec was given, so try the charset in the mail codec = this->codec(); assert( codec ); return codec->toUnicode( bodyDecoded() ); } //----------------------------------------------------------------------------- TQCString KMMessage::mboxMessageSeparator() { TQCString str( KPIM::getFirstEmailAddress( rawHeaderField("From") ) ); if ( str.isEmpty() ) str = "unknown@unknown.invalid"; TQCString dateStr( dateShortStr() ); if ( dateStr.isEmpty() ) { time_t t = ::time( 0 ); dateStr = ctime( &t ); const int len = dateStr.length(); if ( dateStr[len-1] == '\n' ) dateStr.truncate( len - 1 ); } return "From " + str + " " + dateStr + "\n"; } void KMMessage::deleteWhenUnused() { sPendingDeletes << this; } DwBodyPart* KMMessage::findPart( int index ) { int accu = 0; return findPartInternal( getTopLevelPart(), index, accu ); } DwBodyPart* KMMessage::findPartInternal(DwEntity * root, int index, int & accu) { accu++; if ( index < accu ) // should not happen return 0; DwBodyPart *current = dynamic_cast( root ); if ( index == accu ) return current; DwBodyPart *rv = 0; if ( root->Body().FirstBodyPart() ) rv = findPartInternal( root->Body().FirstBodyPart(), index, accu ); if ( !rv && current && current->Next() ) rv = findPartInternal( current->Next(), index, accu ); if ( !rv && root->Body().Message() ) rv = findPartInternal( root->Body().Message(), index, accu ); return rv; }