From f5185c4799fe0e9c31a218dfc8310515ac921c2b Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Wed, 9 Mar 2016 15:20:31 +0100 Subject: Moved framework/mail to framework/domain --- framework/domain/mailtemplates.cpp | 804 +++++++++++++++++++++++++++++++++++++ 1 file changed, 804 insertions(+) create mode 100644 framework/domain/mailtemplates.cpp (limited to 'framework/domain/mailtemplates.cpp') diff --git a/framework/domain/mailtemplates.cpp b/framework/domain/mailtemplates.cpp new file mode 100644 index 00000000..e5ee8533 --- /dev/null +++ b/framework/domain/mailtemplates.cpp @@ -0,0 +1,804 @@ +/* + Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Copyright (c) 2010 Leo Franchi + Copyright (c) 2016 Christian Mollekopf + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +#include "mailtemplates.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "stringhtmlwriter.h" +#include "objecttreesource.h" +#include "csshelper.h" + +#include + +namespace KMime { + namespace Types { +static bool operator==(const KMime::Types::AddrSpec &left, const KMime::Types::AddrSpec &right) +{ + return (left.asString() == right.asString()); +} + +static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right) +{ + return (left.addrSpec().asString() == right.addrSpec().asString()); +} + } +} + +static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KMime::Types::AddrSpecList me) +{ + KMime::Types::Mailbox::List addresses(list); + for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) { + if (me.contains(it->addrSpec())) { + it = addresses.erase(it); + } else { + ++it; + } + } + + return addresses; +} + +void initHeader(const KMime::Message::Ptr &message) +{ + message->removeHeader(); + message->removeHeader(); + message->date()->setDateTime(QDateTime::currentDateTime()); + + const QStringList extraInfo = QStringList() << QSysInfo::prettyProductName(); + message->userAgent()->fromUnicodeString(QString("%1/%2(%3)").arg(QString::fromLocal8Bit("Kube")).arg("0.1").arg(extraInfo.join(",")), "utf-8"); + // This will allow to change Content-Type: + message->contentType()->setMimeType("text/plain"); +} + +QString replacePrefixes(const QString &str, const QStringList &prefixRegExps, + bool replace, const QString &newPrefix) +{ + bool recognized = false; + // construct a big regexp that + // 1. is anchored to the beginning of str (sans whitespace) + // 2. matches at least one of the part regexps in prefixRegExps + QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QStringLiteral(")|(?:"))); + QRegExp rx(bigRegExp, Qt::CaseInsensitive); + if (rx.isValid()) { + QString tmp = str; + if (rx.indexIn(tmp) == 0) { + recognized = true; + if (replace) { + return tmp.replace(0, rx.matchedLength(), newPrefix + QLatin1String(" ")); + } + } + } else { + qWarning() << "bigRegExp = \"" + << bigRegExp << "\"\n" + << "prefix regexp is invalid!"; + // try good ole Re/Fwd: + recognized = str.startsWith(newPrefix); + } + + if (!recognized) { + return newPrefix + QLatin1String(" ") + str; + } else { + return str; + } +} + +QString cleanSubject(const KMime::Message::Ptr &msg, const QStringList &prefixRegExps, bool replace, const QString &newPrefix) +{ + return replacePrefixes(msg->subject()->asUnicodeString(), prefixRegExps, replace, newPrefix); +} + +QString forwardSubject(const KMime::Message::Ptr &msg) +{ + bool replaceForwardPrefix = true; + QStringList forwardPrefixes; + forwardPrefixes << "Fwd:"; + forwardPrefixes << "FW:"; + return cleanSubject(msg, forwardPrefixes, replaceForwardPrefix, QStringLiteral("Fwd:")); +} + +QString replySubject(const KMime::Message::Ptr &msg) +{ + bool replaceReplyPrefix = true; + QStringList replyPrefixes; + //We're escaping the regex escape sequences. awesome + replyPrefixes << "Re\\\\s*:"; + replyPrefixes << "Re[\\\\d+\\\\]:"; + replyPrefixes << "Re\\\\d+:"; + return cleanSubject(msg, replyPrefixes, replaceReplyPrefix, QStringLiteral("Re:")); +} + +QByteArray getRefStr(const KMime::Message::Ptr &msg) +{ + QByteArray firstRef, lastRef, refStr, retRefStr; + int i, j; + + if (auto hdr = msg->references(false)) { + refStr = hdr->as7BitString(false).trimmed(); + } + + if (refStr.isEmpty()) { + return msg->messageID()->as7BitString(false); + } + + 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 += msg->messageID()->as7BitString(false); + return retRefStr; +} + +KMime::Content *createPlainPartContent(const KMime::Message::Ptr &msg, const QString &plainBody) +{ + KMime::Content *textPart = new KMime::Content(msg.data()); + textPart->contentType()->setMimeType("text/plain"); + //FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text + // QTextCodec *charset = selectCharset(m_charsets, plainBody); + // textPart->contentType()->setCharset(charset->name()); + textPart->contentType()->setCharset("utf-8"); + textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); + textPart->fromUnicodeString(plainBody); + return textPart; +} + +KMime::Content *createMultipartAlternativeContent(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody) +{ + KMime::Content *multipartAlternative = new KMime::Content(msg.data()); + multipartAlternative->contentType()->setMimeType("multipart/alternative"); + const QByteArray boundary = KMime::multiPartBoundary(); + multipartAlternative->contentType()->setBoundary(boundary); + + KMime::Content *textPart = createPlainPartContent(msg, plainBody); + multipartAlternative->addContent(textPart); + + KMime::Content *htmlPart = new KMime::Content(msg.data()); + htmlPart->contentType()->setMimeType("text/html"); + //FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text + // QTextCodec *charset = selectCharset(m_charsets, htmlBody); + // htmlPart->contentType()->setCharset(charset->name()); + textPart->contentType()->setCharset("utf-8"); + htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); + htmlPart->fromUnicodeString(htmlBody); + multipartAlternative->addContent(htmlPart); + + return multipartAlternative; +} + +void addProcessedBodyToMessage(const KMime::Message::Ptr &msg, const QString &plainBody, const QString &htmlBody, bool forward) +{ + //FIXME + // MessageCore::ImageCollector ic; + // ic.collectImagesFrom(mOrigMsg.data()); + + // Now, delete the old content and set the new content, which + // is either only the new text or the new text with some attachments. + auto parts = msg->contents(); + foreach (KMime::Content *content, parts) { + msg->removeContent(content, true/*delete*/); + } + + msg->contentType()->clear(); // to get rid of old boundary + + const QByteArray boundary = KMime::multiPartBoundary(); + KMime::Content *const mainTextPart = + htmlBody.isEmpty() ? + createPlainPartContent(msg, plainBody) : + createMultipartAlternativeContent(msg, plainBody, htmlBody); + mainTextPart->assemble(); + + KMime::Content *textPart = mainTextPart; + // if (!ic.images().empty()) { + // textPart = createMultipartRelated(ic, mainTextPart); + // textPart->assemble(); + // } + + // If we have some attachments, create a multipart/mixed mail and + // add the normal body as well as the attachments + KMime::Content *mainPart = textPart; + //FIXME + // if (forward) { + // auto attachments = mOrigMsg->attachments(); + // attachments += mOtp->nodeHelper()->attachmentsOfExtraContents(); + // if (!attachments.isEmpty()) { + // mainPart = createMultipartMixed(attachments, textPart); + // mainPart->assemble(); + // } + // } + + msg->setBody(mainPart->encodedBody()); + msg->setHeader(mainPart->contentType()); + msg->setHeader(mainPart->contentTransferEncoding()); + msg->assemble(); + msg->parse(); +} + +QString plainToHtml(const QString &body) +{ + QString str = body; + str = str.toHtmlEscaped(); + str.replace(QStringLiteral("\n"), QStringLiteral("
\n")); + return str; +} + +//TODO implement this function using a DOM tree parser +void makeValidHtml(QString &body, const QString &headElement) +{ + QRegExp regEx; + regEx.setMinimal(true); + regEx.setPattern(QStringLiteral("")); + + if (!body.isEmpty() && !body.contains(regEx)) { + regEx.setPattern(QStringLiteral("")); + if (!body.contains(regEx)) { + body = QLatin1String("") + body + QLatin1String("
"); + } + regEx.setPattern(QStringLiteral("")); + if (!body.contains(regEx)) { + body = QLatin1String("") + headElement + QLatin1String("") + body; + } + body = QLatin1String("") + body + QLatin1String(""); + } +} + +QString stripSignature(const QString &msg) +{ + // Following RFC 3676, only > before -- + // I prefer to not delete a SB instead of delete good mail content. + const QRegExp sbDelimiterSearch = QRegExp(QLatin1String("(^|\n)[> ]*-- \n")); + // The regular expression to look for prefix change + const QRegExp commonReplySearch = QRegExp(QLatin1String("^[ ]*>")); + + QString res = msg; + int posDeletingStart = 1; // to start looking at 0 + + // While there are SB delimiters (start looking just before the deleted SB) + while ((posDeletingStart = res.indexOf(sbDelimiterSearch, posDeletingStart - 1)) >= 0) { + QString prefix; // the current prefix + QString line; // the line to check if is part of the SB + int posNewLine = -1; + + // Look for the SB beginning + int posSignatureBlock = res.indexOf(QLatin1Char('-'), posDeletingStart); + // The prefix before "-- "$ + if (res.at(posDeletingStart) == QLatin1Char('\n')) { + ++posDeletingStart; + } + + prefix = res.mid(posDeletingStart, posSignatureBlock - posDeletingStart); + posNewLine = res.indexOf(QLatin1Char('\n'), posSignatureBlock) + 1; + + // now go to the end of the SB + while (posNewLine < res.size() && posNewLine > 0) { + // handle the undefined case for mid ( x , -n ) where n>1 + int nextPosNewLine = res.indexOf(QLatin1Char('\n'), posNewLine); + + if (nextPosNewLine < 0) { + nextPosNewLine = posNewLine - 1; + } + + line = res.mid(posNewLine, nextPosNewLine - posNewLine); + + // check when the SB ends: + // * does not starts with prefix or + // * starts with prefix+(any substring of prefix) + if ((prefix.isEmpty() && line.indexOf(commonReplySearch) < 0) || + (!prefix.isEmpty() && line.startsWith(prefix) && + line.mid(prefix.size()).indexOf(commonReplySearch) < 0)) { + posNewLine = res.indexOf(QLatin1Char('\n'), posNewLine) + 1; + } else { + break; // end of the SB + } + } + + // remove the SB or truncate when is the last SB + if (posNewLine > 0) { + res.remove(posDeletingStart, posNewLine - posDeletingStart); + } else { + res.truncate(posDeletingStart); + } + } + + return res; +} + +QString plainMessageText(MessageViewer::ObjectTreeParser &otp, bool aStripSignature) +{ + QString result = otp.plainTextContent(); + if (result.isEmpty()) { //HTML-only mails + QWebPage doc; + doc.mainFrame()->setHtml(otp.htmlContent()); + result = doc.mainFrame()->toPlainText(); + } + + if (aStripSignature) { + result = stripSignature(result); + } + + return result; +} + +QString htmlMessageText(MessageViewer::ObjectTreeParser &otp, bool aStripSignature, QString &headElement) +{ + QString htmlElement = otp.htmlContent(); + + if (htmlElement.isEmpty()) { //plain mails only + QString htmlReplace = otp.plainTextContent().toHtmlEscaped(); + htmlReplace = htmlReplace.replace(QStringLiteral("\n"), QStringLiteral("
")); + htmlElement = QStringLiteral("%1\n").arg(htmlReplace); + } + + //QWebPage relies on this + Q_ASSERT(QApplication::style()); + QWebPage page; + page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false); + page.settings()->setAttribute(QWebSettings::JavaEnabled, false); + page.settings()->setAttribute(QWebSettings::PluginsEnabled, false); + page.settings()->setAttribute(QWebSettings::AutoLoadImages, false); + + page.currentFrame()->setHtml(htmlElement); + + //TODO to be tested/verified if this is not an issue + page.settings()->setAttribute(QWebSettings::JavascriptEnabled, true); + const QString bodyElement = page.currentFrame()->evaluateJavaScript( + QStringLiteral("document.getElementsByTagName('body')[0].innerHTML")).toString(); + + headElement = page.currentFrame()->evaluateJavaScript( + QStringLiteral("document.getElementsByTagName('head')[0].innerHTML")).toString(); + + page.settings()->setAttribute(QWebSettings::JavascriptEnabled, false); + + if (!bodyElement.isEmpty()) { + if (aStripSignature) { + //FIXME strip signature works partially for HTML mails + return stripSignature(bodyElement); + } + return bodyElement; + } + + if (aStripSignature) { + //FIXME strip signature works partially for HTML mails + return stripSignature(htmlElement); + } + return htmlElement; +} + +QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString) +{ + QString result; + + if (wildString.isEmpty()) { + return wildString; + } + + unsigned int strLength(wildString.length()); + for (uint i = 0; i < strLength;) { + QChar ch = wildString[i++]; + if (ch == QLatin1Char('%') && i < strLength) { + ch = wildString[i++]; + switch (ch.toLatin1()) { + case 'f': { // sender's initals + if (fromDisplayString.isEmpty()) { + break; + } + + uint j = 0; + const unsigned int strLength(fromDisplayString.length()); + for (; j < strLength && fromDisplayString[j] > QLatin1Char(' '); ++j) + ; + for (; j < strLength && fromDisplayString[j] <= QLatin1Char(' '); ++j) + ; + result += fromDisplayString[0]; + if (j < strLength && fromDisplayString[j] > QLatin1Char(' ')) { + result += fromDisplayString[j]; + } else if (strLength > 1) { + if (fromDisplayString[1] > QLatin1Char(' ')) { + result += fromDisplayString[1]; + } + } + } + break; + case '_': + result += QLatin1Char(' '); + break; + case '%': + result += QLatin1Char('%'); + break; + default: + result += QLatin1Char('%'); + result += ch; + break; + } + } else { + result += ch; + } + } + return result; +} + +QString quotedPlainText(const QString &selection, const QString &fromDisplayString) +{ + QString content = selection; + // Remove blank lines at the beginning: + const int firstNonWS = content.indexOf(QRegExp(QLatin1String("\\S"))); + const int lineStart = content.lastIndexOf(QLatin1Char('\n'), firstNonWS); + if (lineStart >= 0) { + content.remove(0, static_cast(lineStart)); + } + + const auto quoteString = QStringLiteral("> "); + const QString indentStr = formatQuotePrefix(quoteString, fromDisplayString); + //FIXME + // if (TemplateParserSettings::self()->smartQuote() && mWrap) { + // content = MessageCore::StringUtil::smartQuote(content, mColWrap - indentStr.length()); + // } + content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr); + content.prepend(indentStr); + content += QLatin1Char('\n'); + + return content; +} + +QString quotedHtmlText(const QString &selection) +{ + QString content = selection; + //TODO 1) look for all the variations of
and remove the blank lines + //2) implement vertical bar for quoted HTML mail. + //3) After vertical bar is implemented, If a user wants to edit quoted message, + // then the
tags below should open and close as when required. + + //Add blockquote tag, so that quoted message can be differentiated from normal message + content = QLatin1String("
") + content + QLatin1String("
"); + return content; +} + +void applyCharset(const KMime::Message::Ptr msg, const KMime::Message::Ptr &origMsg) +{ + // first convert the body from its current encoding to unicode representation + QTextCodec *bodyCodec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset())); + if (!bodyCodec) { + bodyCodec = KCharsets::charsets()->codecForName(QStringLiteral("UTF-8")); + } + + const QString body = bodyCodec->toUnicode(msg->body()); + + // then apply the encoding of the original message + msg->contentType()->setCharset(origMsg->contentType()->charset()); + + QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset())); + if (!codec) { + qCritical() << "Could not get text codec for charset" << msg->contentType()->charset(); + } else if (!codec->canEncode(body)) { // charset can't encode body, fall back to preferred + const QStringList charsets /*= preferredCharsets() */; + + QList chars; + chars.reserve(charsets.count()); + foreach (const QString &charset, charsets) { + chars << charset.toLatin1(); + } + + //FIXME + QByteArray fallbackCharset/* = selectCharset(chars, body)*/; + if (fallbackCharset.isEmpty()) { // UTF-8 as fall-through + fallbackCharset = "UTF-8"; + } + + codec = KCharsets::charsets()->codecForName(QString::fromLatin1(fallbackCharset)); + msg->setBody(codec->fromUnicode(body)); + } else { + msg->setBody(codec->fromUnicode(body)); + } +} + +enum ReplyStrategy { + ReplyList, + ReplySmart, + ReplyAll, + ReplyAuthor, + ReplyNone +}; + +KMime::Message::Ptr MailTemplates::reply(const KMime::Message::Ptr &origMsg) +{ + //FIXME + const bool alwaysPlain = true; + //FIXME + const ReplyStrategy replyStrategy = ReplySmart; + KMime::Message::Ptr msg(new KMime::Message); + //FIXME + //Personal email addresses + KMime::Types::AddrSpecList me; + KMime::Types::Mailbox::List toList; + KMime::Types::Mailbox::List replyToList; + KMime::Types::Mailbox::List mailingListAddresses; + + // const uint originalIdentity = identityUoid(origMsg); + initHeader(msg); + replyToList = origMsg->replyTo()->mailboxes(); + + msg->contentType()->setCharset("utf-8"); + + if (origMsg->headerByType("List-Post") && + origMsg->headerByType("List-Post")->asUnicodeString().contains(QStringLiteral("mailto:"), Qt::CaseInsensitive)) { + + const QString listPost = origMsg->headerByType("List-Post")->asUnicodeString(); + QRegExp rx(QStringLiteral("]+)@([^>]+)>"), Qt::CaseInsensitive); + if (rx.indexIn(listPost, 0) != -1) { // matched + KMime::Types::Mailbox mailbox; + mailbox.fromUnicodeString(rx.cap(1) + QLatin1Char('@') + rx.cap(2)); + mailingListAddresses << mailbox; + } + } + + switch (replyStrategy) { + case ReplySmart: { + if (auto hdr = origMsg->headerByType("Mail-Followup-To")) { + toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); + } else if (!replyToList.isEmpty()) { + toList = replyToList; + } else if (!mailingListAddresses.isEmpty()) { + toList = (KMime::Types::Mailbox::List() << mailingListAddresses.at(0)); + } else { + // doesn't seem to be a mailing list, reply to From: address + toList = origMsg->from()->mailboxes(); + + bool listContainsMe = false; + for (const auto &m : me) { + KMime::Types::Mailbox mailbox; + mailbox.setAddress(m); + if (toList.contains(mailbox)) { + listContainsMe = true; + } + } + if (listContainsMe) { + // sender seems to be one of our own identities, so we assume that this + // is a reply to a "sent" mail where the users wants to add additional + // information for the recipient. + toList = origMsg->to()->mailboxes(); + } + } + // strip all my addresses from the list of recipients + const KMime::Types::Mailbox::List recipients = toList; + + toList = stripMyAddressesFromAddressList(recipients, me); + + // ... unless the list contains only my addresses (reply to self) + if (toList.isEmpty() && !recipients.isEmpty()) { + toList << recipients.first(); + } + } + break; + case ReplyList: { + if (auto hdr = origMsg->headerByType("Mail-Followup-To")) { + KMime::Types::Mailbox mailbox; + mailbox.from7BitString(hdr->as7BitString(false)); + toList << mailbox; + } else if (!mailingListAddresses.isEmpty()) { + toList << mailingListAddresses[ 0 ]; + } else if (!replyToList.isEmpty()) { + // assume a Reply-To header mangling mailing list + toList = replyToList; + } + + //FIXME + // strip all my addresses from the list of recipients + const KMime::Types::Mailbox::List recipients = toList; + toList = stripMyAddressesFromAddressList(recipients, me); + } + break; + case ReplyAll: { + KMime::Types::Mailbox::List recipients; + KMime::Types::Mailbox::List ccRecipients; + + // add addresses from the Reply-To header to the list of recipients + if (!replyToList.isEmpty()) { + recipients = replyToList; + + // strip all possible mailing list addresses from the list of Reply-To addresses + foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) { + foreach (const KMime::Types::Mailbox &recipient, recipients) { + if (mailbox == recipient) { + recipients.removeAll(recipient); + } + } + } + } + + if (!mailingListAddresses.isEmpty()) { + // this is a mailing list message + if (recipients.isEmpty() && !origMsg->from()->asUnicodeString().isEmpty()) { + // The sender didn't set a Reply-to address, so we add the From + // address to the list of CC recipients. + ccRecipients += origMsg->from()->mailboxes(); + qDebug() << "Added" << origMsg->from()->asUnicodeString() << "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() && !origMsg->from()->asUnicodeString().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 += origMsg->from()->mailboxes(); + qDebug() << "Added" << origMsg->from()->asUnicodeString() << "to the list of recipients"; + } + } + + // strip all my addresses from the list of recipients + toList = stripMyAddressesFromAddressList(recipients, me); + + // merge To header and CC header into a list of CC recipients + if (!origMsg->cc()->asUnicodeString().isEmpty() || !origMsg->to()->asUnicodeString().isEmpty()) { + KMime::Types::Mailbox::List list; + if (!origMsg->to()->asUnicodeString().isEmpty()) { + list += origMsg->to()->mailboxes(); + } + if (!origMsg->cc()->asUnicodeString().isEmpty()) { + list += origMsg->cc()->mailboxes(); + } + + foreach (const KMime::Types::Mailbox &mailbox, list) { + if (!recipients.contains(mailbox) && + !ccRecipients.contains(mailbox)) { + ccRecipients += mailbox; + qDebug() << "Added" << mailbox.prettyAddress() << "to the list of CC recipients"; + } + } + } + + if (!ccRecipients.isEmpty()) { + // strip all my addresses from the list of CC recipients + ccRecipients = stripMyAddressesFromAddressList(ccRecipients, me); + + // in case of a reply to self, toList might be empty. if that's the case + // then propagate a cc recipient to To: (if there is any). + if (toList.isEmpty() && !ccRecipients.isEmpty()) { + toList << ccRecipients.at(0); + ccRecipients.pop_front(); + } + + foreach (const KMime::Types::Mailbox &mailbox, ccRecipients) { + msg->cc()->addAddress(mailbox); + } + } + + if (toList.isEmpty() && !recipients.isEmpty()) { + // reply to self without other recipients + toList << recipients.at(0); + } + } + break; + case ReplyAuthor: { + if (!replyToList.isEmpty()) { + KMime::Types::Mailbox::List recipients = replyToList; + + // strip the mailing list post address from the list of Reply-To + // addresses since we want to reply in private + foreach (const KMime::Types::Mailbox &mailbox, mailingListAddresses) { + foreach (const KMime::Types::Mailbox &recipient, recipients) { + if (mailbox == recipient) { + recipients.removeAll(recipient); + } + } + } + + if (!recipients.isEmpty()) { + toList = recipients; + } else { + // there was only the mailing list post address in the Reply-To header, + // so use the From address instead + toList = origMsg->from()->mailboxes(); + } + } else if (!origMsg->from()->asUnicodeString().isEmpty()) { + toList = origMsg->from()->mailboxes(); + } + } + break; + case ReplyNone: + // the addressees will be set by the caller + break; + } + + foreach (const KMime::Types::Mailbox &mailbox, toList) { + msg->to()->addAddress(mailbox); + } + + const QByteArray refStr = getRefStr(origMsg); + if (!refStr.isEmpty()) { + msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); + } + + //In-Reply-To = original msg-id + msg->inReplyTo()->from7BitString(origMsg->messageID()->as7BitString(false)); + + msg->subject()->fromUnicodeString(replySubject(origMsg), "utf-8"); + + auto definedLocale = QLocale::system(); + + //TODO set empty source instead + StringHtmlWriter htmlWriter; + QImage paintDevice; + CSSHelper cssHelper(&paintDevice); + MessageViewer::NodeHelper nodeHelper; + ObjectTreeSource source(&htmlWriter, &cssHelper); + MessageViewer::ObjectTreeParser otp(&source, &nodeHelper); + otp.setAllowAsync(false); + otp.parseObjectTree(origMsg.data()); + + //Add quoted body + QString plainBody; + QString htmlBody; + + //On $datetime you wrote: + const QDateTime date = origMsg->date()->dateTime(); + const auto dateTimeString = QString("%1 %2").arg(definedLocale.toString(date.date(), QLocale::LongFormat)).arg(definedLocale.toString(date.time(), QLocale::LongFormat)); + const auto onDateYouWroteLine = QString("On %1 you wrote:").arg(dateTimeString); + plainBody.append(onDateYouWroteLine); + htmlBody.append(plainToHtml(onDateYouWroteLine)); + + //Strip signature for replies + const bool stripSignature = true; + + //Quoted body + QString plainQuote = quotedPlainText(plainMessageText(otp, stripSignature), origMsg->from()->displayString()); + if (plainQuote.endsWith(QLatin1Char('\n'))) { + plainQuote.chop(1); + } + plainBody.append(plainQuote); + QString headElement; + htmlBody.append(quotedHtmlText(htmlMessageText(otp, stripSignature, headElement))); + + if (alwaysPlain) { + htmlBody.clear(); + } else { + makeValidHtml(htmlBody, headElement); + } + + addProcessedBodyToMessage(msg, plainBody, htmlBody, false); + + applyCharset(msg, origMsg); + + msg->assemble(); + + return msg; +} -- cgit v1.2.3