// -*- mode: C++; c-file-style: "gnu" -*- // kmmessage.cpp #include "kmmessage.h" #include // if you do not want GUI elements in here then set ALLOW_GUI to 0. // needed temporarily until KMime is replacing the partNode helper class: #include "partNode.h" #define ALLOW_GUI 1 #include "kmkernel.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 #include "kmmessagetag.h" #include "headerstrategy.h" #include "globalsettings.h" using KMail::HeaderStrategy; #include "kmaddrbook.h" #include "kcursorsaver.h" #include "templateparser.h" using KMail::TemplateParser; #include "stringutil.h" #include "mdnadvicedialog.h" #include #include #include #include #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 #if ALLOW_GUI #include #include "util.h" #endif using namespace KMail; using namespace KMime; struct KMMessageStaticData { KMMessageStaticData() : emptyString(""), headerStrategy( HeaderStrategy::rich() ) { } DwString emptyString; // Values that are set from the config file with KMMessage::readConfig() bool smartQuote : 1, wordWrap : 1; int wrapCol; QStringList prefCharsets; const HeaderStrategy* headerStrategy; QList pendingDeletes; }; K_GLOBAL_STATIC( KMMessageStaticData, s ) //helper static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ); //----------------------------------------------------------------------------- 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(); if ( msgInfo.tagList() ) { if ( !mTagList ) mTagList = new KMMessageTagList(); *mTagList = *msgInfo.tagList(); } else { delete mTagList; mTagList = 0; } 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.clear(); mStatus.setNew(); mEncryptionState = KMMsgEncryptionStateUnknown; mSignatureState = KMMsgSignatureStateUnknown; mMDNSentState = KMMsgMDNStateUnknown; mDate = 0; mUnencryptedMsg = 0; mLastUpdated = 0; mTagList = 0; mCursorPos = 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; if ( other.tagList() ) { if ( !mTagList ) mTagList = new KMMessageTagList(); *mTagList = *other.tagList(); } else { delete mTagList; mTagList = 0; } setDrafts( other.drafts() ); setTemplates( other.templates() ); //mFileName = ""; // we might not want to copy the other messages filename (?) //KMMsgBase::assign( &other ); } //----------------------------------------------------------------------------- KMMessage::~KMMessage() { delete mMsg; kmkernel->undoStack()->msgDestroyed( this ); } //----------------------------------------------------------------------------- void KMMessage::setReferences(const QByteArray& aStr) { if (aStr.isNull()) return; mMsg->Headers().References().FromString(aStr); mNeedsAssembly = true; } //----------------------------------------------------------------------------- QByteArray KMMessage::id() const { DwHeaders& header = mMsg->Headers(); if (header.HasMessageId()) return header.MessageId().AsString().c_str(); 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() && s->pendingDeletes.contains( this ) ) { s->pendingDeletes.removeAll( this ); if ( parent() ) { int idx = parent()->find( this ); if ( idx > 0 ) { parent()->removeMsg( idx ); } } } } bool KMMessage::isUrgent() const { return headerField( "Priority" ).contains( "urgent", Qt::CaseSensitive ) || headerField( "X-Priority" ).startsWith( '2' ) || headerField( "X-Priority" ).startsWith( '1' ); } //----------------------------------------------------------------------------- void KMMessage::setUnencryptedMsg( KMMessage* unencrypted ) { delete mUnencryptedMsg; mUnencryptedMsg = unencrypted; } //----------------------------------------------------------------------------- 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; } //----------------------------------------------------------------------------- QByteArray KMMessage::asString() const { return QByteArray( asDwString().c_str() ); } QByteArray KMMessage::asSendableString() const { KMMessage msg( new DwMessage( *this->mMsg ) ); msg.removePrivateHeaderFields(); msg.removeHeaderField("Bcc"); return msg.asString(); } QByteArray KMMessage::headerAsSendableString() const { KMMessage msg( new DwMessage( *this->mMsg ) ); msg.removePrivateHeaderFields(); msg.removeHeaderField("Bcc"); return msg.headerAsString().toLatin1(); } 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" ); removeHeaderField( "X-KMail-QuotePrefix" ); } //----------------------------------------------------------------------------- void KMMessage::setStatusFields() { char str[2] = { 0, 0 }; setHeaderField( "Status", mStatus.isNew() ? "R" : "RO" ); setHeaderField( "X-Status", mStatus.getStatusStr() ); str[0] = (char)encryptionState(); setHeaderField("X-KMail-EncryptionState", str); str[0] = (char)signatureState(); //kDebug() <<"Setting SignatureState header field to" << str[0]; 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() ); } //---------------------------------------------------------------------------- QString KMMessage::headerAsString() const { DwHeaders& header = mMsg->Headers(); header.Assemble(); if ( header.AsString().empty() ) return QString(); return QString::fromLatin1( header.AsString().c_str() ); } //----------------------------------------------------------------------------- DwMediaType& KMMessage::dwContentType() { return mMsg->Headers().ContentType(); } void KMMessage::fromString( const QByteArray & ba, bool setStatus ) { return fromDwString( DwString( ba.data(), ba.size() ), setStatus ); } void KMMessage::fromDwString(const DwString& str, bool aSetStatus) { delete mMsg; mMsg = new DwMessage; mMsg->FromString( str ); mMsg->Parse(); if (aSetStatus) { setStatus(headerField("Status").toLatin1(), headerField("X-Status").toLatin1()); if ( !headerField( "X-KMail-EncryptionState" ).isEmpty() ) setEncryptionStateChar( headerField( "X-KMail-EncryptionState" ).at(0) ); if ( !headerField( "X-KMail-SignatureState" ).isEmpty() ) setSignatureStateChar( headerField( "X-KMail-SignatureState" ).at(0)); if ( !headerField("X-KMail-MDN-Sent").isEmpty() ) setMDNSentState( static_cast( headerField("X-KMail-MDN-Sent").at(0).toLatin1() ) ); } if (attachmentState() == KMMsgAttachmentUnknown && readyToShow()) updateAttachmentState(); mNeedsAssembly = false; mDate = date(); } //----------------------------------------------------------------------------- QString KMMessage::formatString( const QString &aStr ) const { QString result; 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 '_': result += ' '; break; case '%': result += '%'; break; default: result += '%'; result += ch; break; } } else { result += ch; } } return result; } //----------------------------------------------------------------------------- void KMMessage::parseTextStringFromDwPart( partNode * root, QByteArray& parsedString, const QTextCodec*& codec, bool& isHTML ) const { if ( !root ) return; isHTML = false; // initialy parse the complete message to decrypt any encrypted parts { ObjectTreeParser otp( 0, 0, true, false, true ); otp.parseObjectTree( root ); } partNode * curNode = root->findType( DwMime::kTypeText, DwMime::kSubtypeUnknown, true, false ); kDebug() << ( curNode ? "text part found!\n" : "sorry, no text node!\n" ); 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(); } } //----------------------------------------------------------------------------- QString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const { QByteArray parsedString; bool isHTML = false; const QTextCodec * codec = 0; partNode * root = partNode::fromMessage( this ); if ( !root ) return QString(); parseTextStringFromDwPart( root, parsedString, codec, isHTML ); delete root; if ( mOverrideCodec || !codec ) codec = this->codec(); if ( parsedString.isEmpty() ) return QString(); bool clearSigned = false; QString result; // decrypt if ( allowDecryption ) { QList pgpBlocks; QList 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 ) { KHTMLPart 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 StringUtil::stripSignature( result, clearSigned ); else return result; } QString KMMessage::asQuotedString( const QString& aIndentStr, const QString& selection /*.clear() */, bool aStripSignature /* = true */, bool allowDecryption /* = true */) const { QString content = selection.isEmpty() ? asPlainText( aStripSignature, allowDecryption ) : selection ; // Remove blank lines at the beginning: const int firstNonWS = content.indexOf( QRegExp( "\\S" ) ); const int lineStart = content.lastIndexOf( '\n', firstNonWS ); if ( lineStart >= 0 ) content.remove( 0, static_cast( lineStart ) ); const QString indentStr = formatString( aIndentStr ); content.replace( '\n', '\n' + indentStr ); content.prepend( indentStr ); content += '\n'; if ( s->smartQuote && s->wordWrap ) return StringUtil::smartQuote( content, s->wrapCol ); return content; } //----------------------------------------------------------------------------- KMMessage* KMMessage::createReply( ReplyStrategy replyStrategy, const QString &selection /*.clear() */, bool noQuote /* = false */, bool allowDecryption /* = true */, bool selectionIsBody /* = false */, const QString &tmpl /* = QString() */ ) { KMMessage* msg = new KMMessage; QString str, mailingListStr, replyToStr, toStr; QStringList mailingListAddresses; QByteArray 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").contains( "mailto:", Qt::CaseInsensitive ) ) { QString listPost = headerField("List-Post"); QRegExp rx( "]+)@([^>]+)>", Qt::CaseInsensitive ); if ( rx.indexIn( listPost, 0 ) != -1 ) // matched mailingListAddresses << rx.cap(1) + '@' + rx.cap(2); } switch( replyStrategy ) { case 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(); replyAll = false; } // strip all my addresses from the list of recipients QStringList recipients = KPIMUtils::splitAddressList( toStr ); toStr = StringUtil::stripMyAddressesFromAddressList( recipients ).join(", "); // ... unless the list contains only my addresses (reply to self) if ( toStr.isEmpty() && !recipients.isEmpty() ) toStr = recipients[0]; break; } case 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 QStringList recipients = KPIMUtils::splitAddressList( toStr ); toStr = StringUtil::stripMyAddressesFromAddressList( recipients ).join(", "); break; } case ReplyAll : { QStringList recipients; QStringList ccRecipients; // add addresses from the Reply-To header to the list of recipients if( !replyToStr.isEmpty() ) { recipients += KPIMUtils::splitAddressList( replyToStr ); // strip all possible mailing list addresses from the list of Reply-To // addresses for ( QStringList::const_iterator it = mailingListAddresses.constBegin(); it != mailingListAddresses.constEnd(); ++it ) { recipients = StringUtil::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(); kDebug() <<"Added" << from() <<"to the list of CC recipients"; } // 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(); kDebug() <<"Added" << from() <<"to the list of recipients"; } } // strip all my addresses from the list of recipients toStr = StringUtil::stripMyAddressesFromAddressList( recipients ).join(", "); // merge To header and CC header into a list of CC recipients if( !cc().isEmpty() || !to().isEmpty() ) { QStringList list; if (!to().isEmpty()) list += KPIMUtils::splitAddressList(to()); if (!cc().isEmpty()) list += KPIMUtils::splitAddressList(cc()); for( QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it ) { if( !StringUtil::addressIsInAddressList( *it, recipients ) && !StringUtil::addressIsInAddressList( *it, ccRecipients ) ) { ccRecipients += *it; kDebug() <<"Added" << *it <<"to the list of CC recipients"; } } } if ( !ccRecipients.isEmpty() ) { // strip all my addresses from the list of CC recipients ccRecipients = StringUtil::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 ReplyAuthor : { if ( !replyToStr.isEmpty() ) { QStringList recipients = KPIMUtils::splitAddressList( replyToStr ); // strip the mailing list post address from the list of Reply-To // addresses since we want to reply in private for ( QStringList::const_iterator it = mailingListAddresses.constBegin(); it != mailingListAddresses.constEnd(); ++it ) { recipients = StringUtil::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 ReplyNone : { // the addressees will be set by the caller } } msg->setTo(toStr); refStr = getRefStr(); if (!refStr.isEmpty()) msg->setReferences(refStr); //In-Reply-To = original msg-id msg->setReplyToId(msgId()); msg->setSubject( replySubject() ); // If the reply shouldn't be blank, apply the template to the message if ( !noQuote ) { TemplateParser parser( msg, (replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply), selection, s->smartQuote, allowDecryption, selectionIsBody ); if ( !tmpl.isEmpty() ) parser.process( tmpl, this ); else parser.process( this ); } msg->setHeaderField( "X-KMail-QuotePrefix", formatString( GlobalSettings::self()->quoteString() ) ); msg->link( this, MessageStatus::statusReplied() ); 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; } //----------------------------------------------------------------------------- QByteArray KMMessage::getRefStr() const { QByteArray firstRef, lastRef, refStr, retRefStr; int i, j; refStr = headerField("References").trimmed().toLatin1(); if (refStr.isEmpty()) return headerField("Message-Id").toLatin1(); i = refStr.indexOf('<'); j = refStr.indexOf('>'); firstRef = refStr.mid(i, j-i+1); if (!firstRef.isEmpty()) retRefStr = firstRef + ' '; i = refStr.lastIndexOf('<'); j = refStr.lastIndexOf('>'); lastRef = refStr.mid(i, j-i+1); if (!lastRef.isEmpty() && lastRef != firstRef) retRefStr += lastRef + ' '; retRefStr += headerField("Message-Id").toLatin1(); return retRefStr; } KMMessage* KMMessage::createRedirect( const QString &toStr ) { // copy the message 1:1 KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) ); KMMessagePart msgPart; uint id = 0; QString strId = msg->headerField( "X-KMail-Identity" ).trimmed(); if ( !strId.isEmpty()) id = strId.toUInt(); const KPIMIdentities::Identity & ident = kmkernel->identityManager()->identityForUoidOrDefault( id ); // X-KMail-Redirect-From: content QString strByWayOf = QString("%1 (by way of %2 <%3>)") .arg( from() ) .arg( ident.fullName() ) .arg( ident.emailAddr() ); // Resent-From: content QString strFrom = QString("%1 <%2>") .arg( ident.fullName() ) .arg( ident.emailAddr() ); // format the current date to be used in Resent-Date: QString origDate = msg->headerField( "Date" ); msg->setDateToday(); QString 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", StringUtil::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, MessageStatus::statusForwarded() ); return msg; } void KMMessage::sanitizeHeaders( const QStringList& 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( QString::fromLatin1( field->FieldNameStr().c_str() ) ) ) header.RemoveField(field); field = nextField; } mMsg->Assemble(); } //----------------------------------------------------------------------------- KMMessage* KMMessage::createForward( const QString &tmpl /* = QString() */ ) { KMMessage* msg = new KMMessage(); // 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. if ( type() != DwMime::kTypeMultipart && ( type() != DwMime::kTypeText || ( type() == DwMime::kTypeText && subtype() != DwMime::kSubtypeHtml && subtype() != DwMime::kSubtypePlain ) ) ) { 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().c_str() ); // use the headers of the original mail applyHeadersToMessagePart( mMsg->Headers(), &secondPart ); msg->addBodyPart(&secondPart); msg->mNeedsAssembly = true; msg->cleanupHeader(); } // Normal message (multipart or text/plain|html) // Just copy the message, the template parser will do the hard work of // replacing the body text in TemplateParser::addProcessedBodyToMessage() else { msg->fromDwString( this->asDwString() ); DwMediaType oldContentType = msg->mMsg->Headers().ContentType(); msg->sanitizeHeaders(); msg->initFromMessage( this ); // restore the content type, initFromMessage() sets the contents type to // text/plain, via initHeader(), for unclear reasons msg->mMsg->Headers().ContentType().FromString( oldContentType.AsString() ); msg->mMsg->Headers().ContentType().Parse(); msg->mMsg->Assemble(); } msg->setSubject( forwardSubject() ); TemplateParser parser( msg, TemplateParser::Forward, asPlainText( false, false ), false, false, false); if ( !tmpl.isEmpty() ) parser.process( tmpl, this ); else parser.process( this ); msg->link( this, MessageStatus::statusForwarded() ); 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 ) ) { const KCursorSaver saver( Qt::ArrowCursor ); MDNAdviceDialog::MDNAdvice answer; answer = MDNAdviceDialog::questionIgnoreSend( mdnMessageBoxes[i].text, mdnMessageBoxes[i].canDeny ); switch ( answer ) { case MDNAdviceDialog::MDNSend: return 3; case MDNAdviceDialog::MDNSendDenied: return 2; default: case MDNAdviceDialog::MDNIgnore: return 0; } } kWarning() <<"didn't find data for message box \"" << what << "\""; return 0; } KMMessage* KMMessage::createMDN( MDN::ActionMode a, MDN::DispositionType d, bool allowGUI, QList 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; kDebug() <<"mdnSentState() == '" << st <<"'"; #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: QString receiptTo = headerField("Disposition-Notification-To"); if ( receiptTo.trimmed().isEmpty() ) return 0; receiptTo.remove( '\n' ); MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user QString special; // fill in case of error, warning or failure KConfigGroup mdnConfig( KMKernel::config(), "MDN" ); // default: int mode = mdnConfig.readEntry( "default-policy", 0 ); if ( !mode || mode < 0 || mode > 3 ) { // early out for ignore: setMDNSentState( KMMsgMDNIgnore ); return 0; } if ( mode == 1 /* ask */ && !allowGUI ) return 0; // don't setMDNSentState here! // 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. QString notificationOptions = headerField("Disposition-Notification-Options"); if ( notificationOptions.contains( "required", Qt::CaseSensitive ) ) { // ### 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. kDebug() << "KPIMUtils::splitAddressList(receiptTo):" // krazy:exclude=kdebug << KPIMUtils::splitAddressList(receiptTo).join("\n"); if ( KPIMUtils::splitAddressList(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"); QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + '@' + returnPathList.front().domain ; kDebug() <<"clean return path:" << returnPath; if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, Qt::CaseSensitive ) ) { if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( returnPath.isEmpty() ? "mdnReturnPathEmpty" : "mdnReturnPathNotInReceiptTo" ); s = MDN::SentManually; } if ( true ) { if ( mode == 1 ) { // ask assert( allowGUI ); mode = requestAdviceOnMDN( "mdnNormalAsk" ); s = MDN::SentManually; // asked user } switch ( mode ) { case 0: // ignore: setMDNSentState( KMMsgMDNIgnore ); return 0; default: case 1: kFatal(5006) <<"KMMessage::createMDN(): The \"ask\" mode should" << "never appear here!"; break; case 2: // deny d = MDN::Denied; m.clear(); break; case 3: break; } } // extract where to send from: QString 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" ); QString 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.readEntry( "quote-message", 0 ); if ( num < 0 || num > 2 ) num = 0; /* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/ KMMessagePart thirdMsgPart; switch ( num ) { case 1: thirdMsgPart.setTypeStr( "message" ); thirdMsgPart.setSubtypeStr( "rfc822" ); thirdMsgPart.setBody( asSendableString() ); receipt->addBodyPart( &thirdMsgPart ); break; case 2: thirdMsgPart.setTypeStr( "text" ); thirdMsgPart.setSubtypeStr( "rfc822-headers" ); thirdMsgPart.setBody( headerAsSendableString() ); receipt->addBodyPart( &thirdMsgPart ); break; case 0: default: break; }; receipt->setTo( receiptTo ); receipt->setSubject( "Message Disposition Notification" ); receipt->setReplyToId( msgId() ); receipt->setReferences( getRefStr() ); receipt->cleanupHeader(); kDebug() <<"final message:" + receipt->asString(); // // 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; } QString KMMessage::replaceHeadersInString( const QString & s ) const { QString result = s; QRegExp rx( "\\$\\{([a-z0-9-]+)\\}", Qt::CaseInsensitive ); Q_ASSERT( rx.isValid() ); QRegExp rxDate( "\\$\\{date\\}" ); Q_ASSERT( rxDate.isValid() ); QString sDate = KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized, date() ); int idx = 0; if( ( idx = rxDate.indexIn( result, idx ) ) != -1 ) { result.replace( idx, rxDate.matchedLength(), sDate ); } idx = 0; while ( ( idx = rx.indexIn( result, idx ) ) != -1 ) { QString replacement = headerField( rx.cap(1).toLatin1() ); result.replace( idx, rx.matchedLength(), replacement ); idx += replacement.length(); } return result; } KMMessage* KMMessage::createDeliveryReceipt() const { QString str, receiptTo; KMMessage *receipt; receiptTo = headerField("Disposition-Notification-To"); if ( receiptTo.trimmed().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 toLatin1 is correct here as Mail headers should contain // ascii only receipt->setBody(str.toLatin1()); receipt->setAutomaticFields(); return receipt; } void KMMessage::applyIdentity( uint id ) { const KPIMIdentities::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", QString::number( ident.uoid() )); if (ident.transport().isEmpty()) removeHeaderField("X-KMail-Transport"); else setHeaderField("X-KMail-Transport", ident.transport()); if (ident.fcc().isEmpty()) setFcc( QString() ); else setFcc( ident.fcc() ); if (ident.drafts().isEmpty()) setDrafts( QString() ); else setDrafts( ident.drafts() ); if (ident.templates().isEmpty()) setTemplates( QString() ); else setTemplates( ident.templates() ); } //----------------------------------------------------------------------------- void KMMessage::initHeader( uint id ) { applyIdentity( id ); setTo(""); setSubject(""); setDateToday(); // user agent, e.g. KMail/1.9.50 (Windows/5.0; KDE/3.97.1; i686; svn-762186; 2008-01-15) QStringList extraInfo; # if defined KMAIL_SVN_REVISION_STRING && defined KMAIL_SVN_LAST_CHANGE extraInfo << KMAIL_SVN_REVISION_STRING << KMAIL_SVN_LAST_CHANGE; #else #error forgot to include version-kmail.h # endif setHeaderField("User-Agent", KProtocolManager::userAgentForApplication( "KMail", KMAIL_VERSION, extraInfo ) ); // This will allow to change Content-Type: setHeaderField("Content-Type","text/plain"); } uint KMMessage::identityUoid() const { QString idString = headerField("X-KMail-Identity").trimmed(); 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", QString::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; } //----------------------------------------------------------------------------- QString KMMessage::dateStr() const { KConfigGroup general( KMKernel::config(), "General" ); DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); //kDebug()<<"#### Date ="<(general.readEntry( "dateFormat", int( KMime::DateFormatter::Fancy ) )), unixTime, general.readEntry( "customDateFormat" )); } //----------------------------------------------------------------------------- QByteArray KMMessage::dateShortStr() const { DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); QByteArray result = ctime(&unixTime); if (result[result.length()-1]=='\n') result.truncate(result.length()-1); return result; } //----------------------------------------------------------------------------- QString 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 QString(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 QByteArray& aStr) { DwHeaders& header = mMsg->Headers(); header.Date().FromString(aStr); header.Date().Parse(); mNeedsAssembly = true; mDirty = true; if (header.HasDate()) mDate = header.Date().AsUnixTime(); } //----------------------------------------------------------------------------- QString KMMessage::to() const { // handle To same as Cc below, bug 80747 QList rawHeaders = rawHeaderFields( "To" ); QStringList headers; for ( QList::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { headers << QString::fromUtf8(*it); } return KPIMUtils::normalizeAddressesAndDecodeIdn( headers.join( ", " ) ); } //----------------------------------------------------------------------------- void KMMessage::setTo(const QString& aStr) { setHeaderField( "To", aStr, Address ); } //----------------------------------------------------------------------------- QString KMMessage::toStrip() const { return StringUtil::stripEmailAddr( to() ); } //----------------------------------------------------------------------------- QString KMMessage::replyTo() const { return KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(rawHeaderField("Reply-To")) ); } //----------------------------------------------------------------------------- void KMMessage::setReplyTo(const QString& aStr) { setHeaderField( "Reply-To", aStr, Address ); } //----------------------------------------------------------------------------- void KMMessage::setReplyTo(KMMessage* aMsg) { setHeaderField( "Reply-To", aMsg->from(), Address ); } //----------------------------------------------------------------------------- QString KMMessage::cc() const { // get the combined contents of all Cc headers (as workaround for invalid // messages with multiple Cc headers) QList rawHeaders = rawHeaderFields( "Cc" ); QStringList headers; for ( QList::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { headers << QString::fromUtf8(*it); } return KPIMUtils::normalizeAddressesAndDecodeIdn( headers.join( ", " ) ); } //----------------------------------------------------------------------------- void KMMessage::setCc(const QString& aStr) { setHeaderField( "Cc", aStr, Address ); } //----------------------------------------------------------------------------- QString KMMessage::ccStrip() const { return StringUtil::stripEmailAddr( cc() ); } //----------------------------------------------------------------------------- QString KMMessage::bcc() const { return KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(rawHeaderField("Bcc")) ); } //----------------------------------------------------------------------------- void KMMessage::setBcc(const QString& aStr) { setHeaderField( "Bcc", aStr, Address ); } //----------------------------------------------------------------------------- QString KMMessage::fcc() const { return headerField( "X-KMail-Fcc" ); } //----------------------------------------------------------------------------- void KMMessage::setFcc(const QString& aStr) { setHeaderField( "X-KMail-Fcc", aStr ); } //----------------------------------------------------------------------------- void KMMessage::setDrafts(const QString& aStr) { mDrafts = aStr; } //----------------------------------------------------------------------------- void KMMessage::setTemplates(const QString& aStr) { mTemplates = aStr; } //----------------------------------------------------------------------------- QString KMMessage::who() const { if (mParent) return KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(rawHeaderField(mParent->whoField().toUtf8())) ); return from(); } //----------------------------------------------------------------------------- QString KMMessage::from() const { return KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(rawHeaderField("From")) ); } //----------------------------------------------------------------------------- void KMMessage::setFrom(const QString& bStr) { QString aStr = bStr; if (aStr.isNull()) aStr = ""; setHeaderField( "From", aStr, Address ); mDirty = true; } //----------------------------------------------------------------------------- QString KMMessage::fromStrip() const { return StringUtil::stripEmailAddr( from() ); } //----------------------------------------------------------------------------- QString KMMessage::sender() const { AddrSpecList asl = extractAddrSpecs( "Sender" ); if ( asl.empty() ) asl = extractAddrSpecs( "From" ); if ( asl.empty() ) return QString(); return asl.front().asString(); } //----------------------------------------------------------------------------- QString KMMessage::subject() const { return headerField("Subject"); } //----------------------------------------------------------------------------- void KMMessage::setSubject(const QString& aStr) { setHeaderField("Subject",aStr); mDirty = true; } //Reimplement virtuals from KMMsgBase //----------------------------------------------------------------------------- //Different from KMMsgInfo as that reads from the index QString KMMessage::tagString() const { if ( mTagList ) return mTagList->join( "," ); return QString(); } KMMessageTagList *KMMessage::tagList() const { return mTagList; } //----------------------------------------------------------------------------- QString KMMessage::xmark() const { return headerField("X-KMail-Mark"); } //----------------------------------------------------------------------------- void KMMessage::setXMark(const QString& aStr) { setHeaderField("X-KMail-Mark", aStr); mDirty = true; } //----------------------------------------------------------------------------- QString KMMessage::replyToId() const { int leftAngle, rightAngle; QString replyTo, references; replyTo = headerField("In-Reply-To"); // search the end of the (first) message id in the In-Reply-To header rightAngle = replyTo.indexOf( '>' ); if (rightAngle != -1) replyTo.truncate( rightAngle + 1 ); // now search the start of the message id leftAngle = replyTo.lastIndexOf( '<' ); 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] == '<') && ( !replyTo.contains( '"' ) ) ) return replyTo; references = headerField("References"); leftAngle = references.lastIndexOf( '<' ); if (leftAngle != -1) references = references.mid( leftAngle ); rightAngle = references.indexOf( '>' ); 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; } //----------------------------------------------------------------------------- QString KMMessage::replyToIdMD5() const { return base64EncodedMD5( replyToId() ); } //----------------------------------------------------------------------------- QString KMMessage::references() const { int leftAngle, rightAngle; QString references = headerField( "References" ); // keep the last two entries for threading leftAngle = references.lastIndexOf( '<' ); leftAngle = references.lastIndexOf( '<', leftAngle - 1 ); if( leftAngle != -1 ) references = references.mid( leftAngle ); rightAngle = references.lastIndexOf( '>' ); if( rightAngle != -1 ) references.truncate( rightAngle + 1 ); if( !references.isEmpty() && references[0] == '<' ) return references; else return QString(); } //----------------------------------------------------------------------------- QString KMMessage::replyToAuxIdMD5() const { QString result = references(); // references contains two items, use the first one // (the second to last reference) const int rightAngle = result.indexOf( '>' ); if( rightAngle != -1 ) result.truncate( rightAngle + 1 ); return base64EncodedMD5( result ); } //----------------------------------------------------------------------------- QString KMMessage::strippedSubjectMD5() const { return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ ); } //----------------------------------------------------------------------------- QString KMMessage::subjectMD5() const { return base64EncodedMD5( subject(), true /*utf8*/ ); } //----------------------------------------------------------------------------- bool KMMessage::subjectIsPrefixed() const { return subjectMD5() != strippedSubjectMD5(); } //----------------------------------------------------------------------------- void KMMessage::setReplyToId(const QString& aStr) { setHeaderField("In-Reply-To", aStr); mDirty = true; } //----------------------------------------------------------------------------- QString KMMessage::msgId() const { QString msgId = headerField("Message-Id"); // search the end of the message id const int rightAngle = msgId.indexOf( '>' ); if (rightAngle != -1) msgId.truncate( rightAngle + 1 ); // now search the start of the message id const int leftAngle = msgId.lastIndexOf( '<' ); if (leftAngle != -1) msgId = msgId.mid( leftAngle ); return msgId; } //----------------------------------------------------------------------------- QString KMMessage::msgIdMD5() const { return base64EncodedMD5( msgId() ); } //----------------------------------------------------------------------------- void KMMessage::setMsgId(const QString& 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", QByteArray::number((qlonglong)size)); mDirty = true; } //----------------------------------------------------------------------------- ulong KMMessage::UID() const { return headerField( "X-UID", NoEncoding ).toULong(); } //----------------------------------------------------------------------------- void KMMessage::setUID(ulong uid) { setHeaderField( "X-UID", QByteArray::number((qlonglong)uid), Unstructured, false, NoEncoding ); mDirty = true; } //----------------------------------------------------------------------------- AddressList KMMessage::headerAddrField( const QByteArray & aName ) const { return StringUtil::splitAddrField( rawHeaderField( aName ) ); } AddrSpecList KMMessage::extractAddrSpecs( const QByteArray & header ) const { AddressList al = headerAddrField( header ); AddrSpecList result; for ( AddressList::const_iterator ait = al.constBegin() ; ait != al.constEnd() ; ++ait ) for ( MailboxList::const_iterator mit = (*ait).mailboxList.constBegin() ; mit != (*ait).mailboxList.constEnd() ; ++mit ) result.push_back( (*mit).addrSpec() ); return result; } QByteArray KMMessage::rawHeaderField( const QByteArray &name ) const { if ( !name.isEmpty() && mMsg ) { DwHeaders &header = mMsg->Headers(); DwField *field = header.FindField( name.constData() ); if ( !field ) { return QByteArray(); } return header.FieldBody( name.data() ).AsString().c_str(); } return QByteArray(); } QList KMMessage::rawHeaderFields( const QByteArray& field ) const { if ( field.isEmpty() || !mMsg->Headers().FindField( field ) ) return QList(); std::vector v = mMsg->Headers().AllFieldBodies( field.data() ); QList headerFields; for ( uint i = 0; i < v.size(); ++i ) { headerFields.append( v[i]->AsString().c_str() ); } return headerFields; } QString KMMessage::headerField( const QByteArray& aName, EncodingMode encodingMode ) const { if ( aName.isEmpty() ) { return QString(); } if ( !mMsg->Headers().FindField( aName ) ) { return QString(); } const char *fieldValue = mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(); if ( encodingMode == NoEncoding ) { return QString( fieldValue ); } else { return decodeRFC2047String( fieldValue, charset() ); } } QStringList KMMessage::headerFields( const QByteArray& field ) const { if ( field.isEmpty() || !mMsg->Headers().FindField( field ) ) return QStringList(); std::vector v = mMsg->Headers().AllFieldBodies( field.data() ); QStringList headerFields; for ( uint i = 0; i < v.size(); ++i ) { headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) ); } return headerFields; } //----------------------------------------------------------------------------- void KMMessage::removeHeaderField(const QByteArray& aName) { DwHeaders & header = mMsg->Headers(); DwField * field = header.FindField(aName); if (!field) return; header.RemoveField(field); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::removeHeaderFields(const QByteArray& aName) { DwHeaders & header = mMsg->Headers(); while ( DwField * field = header.FindField(aName) ) { header.RemoveField(field); mNeedsAssembly = true; } } //----------------------------------------------------------------------------- void KMMessage::setHeaderField( const QByteArray& aName, const QString& bValue, HeaderFieldType type, bool prepend, EncodingMode encodingMode ) { #if 0 if ( type != Unstructured ) kDebug() << "( \"" << aName <<"\", \"" << bValue << "\"," << type << ")"; #endif if (aName.isEmpty()) return; DwHeaders& header = mMsg->Headers(); DwString str; DwField* field; QByteArray aValue; if (!bValue.isEmpty()) { QString value = bValue; if ( type == Address ) value = KPIMUtils::normalizeAddressesAndEncodeIdn( value ); #if 0 if ( type != Unstructured ) kDebug() <<"value: \"" << value <<"\""; #endif if ( encodingMode == NoEncoding ) { aValue = value.toAscii(); Q_ASSERT( QString::fromAscii( aValue ) == bValue ); } else { QByteArray encoding = autoDetectCharset( charset(), s->prefCharsets, value ); if (encoding.isEmpty()) encoding = "utf-8"; aValue = encodeRFC2047String( value, encoding ); } #if 0 if ( type != Unstructured ) kDebug() <<"aValue: \"" << aValue <<"\""; #endif } // FIXME PORTING str = (const char*)aName; if (str[str.length()-1] != ':') str += ": "; else str += ' '; if ( !aValue.isEmpty() ) str += (const char*)aValue; 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; } //----------------------------------------------------------------------------- QByteArray KMMessage::typeStr() const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().TypeStr().c_str(); else return ""; } //----------------------------------------------------------------------------- int KMMessage::type( DwEntity *entity ) const { if ( !entity ) entity = mMsg; DwHeaders& header = entity->Headers(); if ( header.HasContentType() ) return header.ContentType().Type(); else return DwMime::kTypeNull; } //----------------------------------------------------------------------------- void KMMessage::setTypeStr(const QByteArray& aStr) { dwContentType().SetTypeStr(DwString(aStr)); dwContentType().Parse(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setType(int aType) { dwContentType().SetType(aType); dwContentType().Assemble(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- QByteArray 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 QByteArray& 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 QByteArray& attr, const QByteArray& 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 QByteArray& attr, const QByteArray& val) { if (mNeedsAssembly) mMsg->Assemble(); mNeedsAssembly = false; setDwMediaTypeParam( dwContentType(), attr, val ); mNeedsAssembly = true; } //----------------------------------------------------------------------------- QByteArray 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 QByteArray& 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; } } //----------------------------------------------------------------------------- QByteArray KMMessage::body() const { DwString body = mMsg->Body().AsString(); QByteArray str = body.c_str(); kWarning( str.length() != static_cast( body.length() ), 5006 ) << "KMMessage::body(): body is binary but used as text!"; return str; } //----------------------------------------------------------------------------- QByteArray KMMessage::bodyDecodedBinary() 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; } int len = dwstr.size(); QByteArray ba(len,'\0'); memcpy(ba.data(),dwstr.data(),len); return ba; } //----------------------------------------------------------------------------- QByteArray 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; } QByteArray result = Util::ByteArray( dwstr ); //kWarning(qstrlen(result) != dwstr.size(), 5006) // << "KMMessage::bodyDecoded(): body is binary but used as text!"; return result; } //----------------------------------------------------------------------------- void KMMessage::setBodyAndGuessCte( const QByteArray& aBuf, QList & allowedCte, bool allow8Bit, bool willBeSigned, DwEntity *entity ) { if ( !entity ) entity = mMsg; CharFreq cf( aBuf ); // it's safe to pass null arrays allowedCte = StringUtil::determineAllowedCtes( cf, allow8Bit, willBeSigned ); setCte( allowedCte[0], entity ); // choose best fitting setBodyEncodedBinary( aBuf, entity ); } //----------------------------------------------------------------------------- void KMMessage::setBodyEncoded(const QByteArray& aStr) { // Qt4: QCString and QByteArray have been merged; this method can be cleaned up setBodyEncodedBinary( aStr ); } //----------------------------------------------------------------------------- void KMMessage::setBodyEncodedBinary( const QByteArray& bodyStr, DwEntity *entity ) { if ( !entity ) entity = mMsg; DwString dwSrc = Util::dwString( bodyStr ); 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 QByteArray& aStr) { mMsg->Body().FromString(aStr.data()); mNeedsAssembly = true; } //----------------------------------------------------------------------------- void KMMessage::setMultiPartBody( const QByteArray & 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(); QList< 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.last(); 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; QList< 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.last(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } return idx; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const { DwBodyPart *part, *curpart; QList< 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.last(); parts.removeLast(); } if (curpart) curpart = curpart->Next(); } return part; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const { DwBodyPart *part, *curpart; QList< 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() ) { kDebug() << curpart->Headers().ContentType().TypeStr().c_str() << " " << curpart->Headers().ContentType().SubtypeStr().c_str(); } 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.last(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } } return part; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::findDwBodyPart( const QByteArray& type, const QByteArray& subtype ) const { DwBodyPart *part, *curpart; QList< 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() ) { kDebug() << curpart->Headers().ContentType().TypeStr().c_str() << " " << curpart->Headers().ContentType().SubtypeStr().c_str(); } 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.last(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } } return part; } void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ) { // Content-type QByteArray 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 (!qstricmp(param->Attribute().c_str(), "charset")) aPart->setCharset(QByteArray(param->Value().c_str()).toLower()); else if (!qstrnicmp(param->Attribute().c_str(), "name*", 5)) aPart->setName(KMMsgBase::decodeRFC2231String( param->Value().c_str())); 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(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 QString partId( aDwBodyPart->partId() ); aPart->setPartSpecifier( partId ); DwHeaders& headers = aDwBodyPart->Headers(); applyHeadersToMessagePart( headers, aPart ); // Body if (withBody) aPart->setBody( aDwBodyPart->Body().AsString().c_str() ); else aPart->setBody( "" ); // Content-id if ( headers.HasContentId() ) { const QByteArray 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(""); 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", aIdx ) ); } } //----------------------------------------------------------------------------- void KMMessage::deleteBodyParts() { mMsg->Body().DeleteBodyParts(); } //----------------------------------------------------------------------------- DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) { DwBodyPart* part = DwBodyPart::NewBodyPart(s->emptyString, 0); if ( !aPart ) return part; QByteArray charset = aPart->charset(); QByteArray type = aPart->typeStr(); QByteArray subtype = aPart->subtypeStr(); QByteArray cte = aPart->cteStr(); QByteArray contDesc = aPart->contentDescriptionEncoded(); QByteArray contDisp = aPart->contentDisposition(); QByteArray contID = aPart->contentId(); QByteArray encoding = autoDetectCharset(charset, s->prefCharsets, aPart->name()); if (encoding.isEmpty()) encoding = "utf-8"; QByteArray name = KMMsgBase::encodeRFC2231String(aPart->name(), encoding); bool RFC2231encoded = aPart->name() != QString(name); QByteArray 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); } } QByteArray additionalParam = aPart->additionalCTypeParamStr(); if( !additionalParam.isEmpty() ) { QByteArray parAV; DwString parA, parV; int iL, i1, i2, iM; iL = additionalParam.length(); i1 = 0; i2 = additionalParam.indexOf(';', i1); while ( i1 < iL ) { if( -1 == i2 ) i2 = iL; if( i1+1 < i2 ) { parAV = additionalParam.mid( i1, (i2-i1) ); iM = parAV.indexOf('='); 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.indexOf( ';', i1 ); } } 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()) { QByteArray encoding = autoDetectCharset(charset, s->prefCharsets, aPart->parameterValue()); if (encoding.isEmpty()) encoding = "utf-8"; QByteArray paramValue; paramValue = KMMsgBase::encodeRFC2231String(aPart->parameterValue(), encoding); DwParameter *param = new DwParameter; if (aPart->parameterValue() != QString(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 ); if ( !contID.isEmpty() ) headers.ContentId().FromString( contID ); if (!aPart->body().isNull()) part->Body().FromString(aPart->body()); else part->Body().FromString(""); if (!aPart->partSpecifier().isNull()) part->SetPartId( DwString(aPart->partSpecifier().toLatin1()) ); 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 ); } //----------------------------------------------------------------------------- void KMMessage::readConfig() { KMMsgBase::readConfig(); KConfigGroup config( KMKernel::config(), "General" ); { // area for config group "Composer" KConfigGroup config( KMKernel::config(), "Composer" ); s->smartQuote = GlobalSettings::self()->smartQuote(); s->wordWrap = GlobalSettings::self()->wordWrap(); s->wrapCol = GlobalSettings::self()->lineWrapWidth(); if ((s->wrapCol == 0) || (s->wrapCol > 78)) s->wrapCol = 78; if (s->wrapCol < 30) s->wrapCol = 30; s->prefCharsets = config.readEntry("pref-charsets", QStringList() ); } { // area for config group "Reader" KConfigGroup config( KMKernel::config(), "Reader" ); s->headerStrategy = HeaderStrategy::create( config.readEntry( "header-set-displayed", "rich" ) ); } } //----------------------------------------------------------------------------- QByteArray 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 QByteArray &charset, DwEntity *entity ) { kWarning( type( entity ) != DwMime::kTypeText ) << "Trying to set a charset for a non-textual mimetype." << endl << "Fix this caller:" << endl << "====================================================================" << endl << kBacktrace( 5 ) << 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(); QByteArray lowerCharset = charset; kAsciiToLower( lowerCharset.data() ); param->SetValue( DwString( lowerCharset ) ); mType.Assemble(); } //----------------------------------------------------------------------------- void KMMessage::setStatus(const MessageStatus& 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, const MessageStatus& aStatus ) { Q_ASSERT( aStatus.isReplied() || aStatus.isForwarded() || aStatus.isDeleted() ); QString message = headerField( "X-KMail-Link-Message" ); if ( !message.isEmpty() ) message += ','; QString type = headerField( "X-KMail-Link-Type" ); if ( !type.isEmpty() ) type += ','; message += QString::number( aMsg->getMsgSerNum() ); if ( aStatus.isReplied() ) type += "reply"; else if ( aStatus.isForwarded() ) type += "forward"; else if ( aStatus.isDeleted() ) type += "deleted"; setHeaderField( "X-KMail-Link-Message", message ); setHeaderField( "X-KMail-Link-Type", type ); } //----------------------------------------------------------------------------- void KMMessage::getLink(int n, ulong *retMsgSerNum, MessageStatus& retStatus) const { *retMsgSerNum = 0; retStatus.clear(); QString message = headerField("X-KMail-Link-Message"); QString 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" ) retStatus.setReplied(); else if ( type == "forward" ) retStatus.setForwarded(); else if ( type == "deleted" ) retStatus.setDeleted(); } } //----------------------------------------------------------------------------- DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const QString & 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 QString partSpecifier, const QByteArray & data) { DwString content( data.data(), data.size() ); if ( numBodyParts() > 0 && partSpecifier != "0" && partSpecifier != "TEXT" ) { QString specifier = partSpecifier; if ( partSpecifier.endsWith( QLatin1String(".HEADER") ) || partSpecifier.endsWith( QLatin1String(".MIME") ) ) { // get the parent bodypart specifier = partSpecifier.section( '.', 0, -2 ); } // search for the bodypart mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier ); if (!mLastUpdated) { kWarning() << "Can not find part" << specifier; return; } if ( partSpecifier.endsWith( QLatin1String(".MIME") ) ) { // update headers // get rid of EOL content.resize( content.length()-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( QLatin1String(".HEADER") ) ) { // update header of embedded message mLastUpdated->Body().Message()->Headers().FromString( content ); mLastUpdated->Body().Message()->Headers().Parse(); } else { // update body mLastUpdated->Body().FromString( content ); QString 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( QLatin1String(".HEADER") ) ) { // notify observers notify(); } } //----------------------------------------------------------------------------- void KMMessage::updateAttachmentState( DwBodyPart *part ) { if ( !part ) { part = getFirstDwBodyPart(); } if ( !part ) { // kDebug() <<"updateAttachmentState - no part!"; if ( mStatus.hasAttachment() ) { toggleStatus( MessageStatus::statusHasAttachment() ); } 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(); } } } 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( MessageStatus::statusHasAttachment() ); } 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 && mStatus.hasAttachment() ) { toggleStatus( MessageStatus::statusHasAttachment() ); } } void KMMessage::setBodyFromUnicode( const QString &str, DwEntity *entity ) { QByteArray encoding = KMMsgBase::autoDetectCharset( charset(), preferredCharsets(), str ); if ( encoding.isEmpty() ) { encoding = "utf-8"; } const QTextCodec * codec = KMMsgBase::codecForName( encoding ); assert( codec ); QList dummy; setCharset( encoding, entity ); setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */, false, entity ); } const QTextCodec * KMMessage::codec() const { const QTextCodec *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().toLatin1() ); } if ( !c ) { // no charset means us-ascii (RFC 2045), so using local encoding should // be okay c = kmkernel->networkCodec(); } assert( c ); return c; } QString KMMessage::bodyToUnicode(const QTextCodec *codec) const { if ( !codec ) { // No codec was given, so try the charset in the mail codec = this->codec(); } assert( codec ); return codec->toUnicode( bodyDecoded() ); } //----------------------------------------------------------------------------- QByteArray KMMessage::mboxMessageSeparator() { QByteArray str( KPIMUtils::firstEmailAddress( rawHeaderField("From") ) ); if ( str.isEmpty() ) str = "unknown@unknown.invalid"; QByteArray 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() { s->pendingDeletes << this; } QByteArray KMMessage::defaultCharset() { QByteArray retval; if (!s->prefCharsets.isEmpty()) retval = s->prefCharsets[0].toLatin1(); if (retval.isEmpty() || (retval == "locale")) { retval = QByteArray(kmkernel->networkCodec()->name()); kAsciiToLower( retval.data() ); } if (retval == "jisx0208.1983-0") retval = "iso-2022-jp"; else if (retval == "ksc5601.1987-0") retval = "euc-kr"; return retval; } const QStringList &KMMessage::preferredCharsets() { return s->prefCharsets; } #ifndef NDEBUG void KMMessage::dump( DwEntity *entity, int level ) { if ( !entity ) { entity = mMsg; entity->Assemble(); } QString spaces; for ( int i = 1; i <= level; i++ ) spaces += " "; kDebug() << QString( spaces + "Headers of entity " + entity->partId() + ':' ); kDebug() << QString( spaces + entity->Headers().AsString().c_str() ); kDebug() << QString( spaces + "Body of entity " + entity->partId() + ':' ); kDebug() << QString( spaces + entity->Body().AsString().c_str() ); DwBodyPart *current = entity->Body().FirstBodyPart(); while ( current ) { dump( current, level + 1 ); current = current->Next(); } } #endif