diff --git a/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp b/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp index dbb2cadd..7e983608 100644 --- a/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp +++ b/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp @@ -1,137 +1,136 @@ /* Copyright (C) 2014-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "followupreminderselectdatedialog.h" #include #include #include #include #include #include #include #include #include #include #include -#include using namespace MessageComposer; class MessageComposer::FollowUpReminderSelectDateDialogPrivate { public: FollowUpReminderSelectDateDialogPrivate() { } KDateComboBox *mDateComboBox = nullptr; Akonadi::CollectionComboBox *mCollectionCombobox = nullptr; QPushButton *mOkButton = nullptr; }; FollowUpReminderSelectDateDialog::FollowUpReminderSelectDateDialog(QWidget *parent, QAbstractItemModel *model) : QDialog(parent) , d(new MessageComposer::FollowUpReminderSelectDateDialogPrivate) { setWindowTitle(i18n("Select Date")); QVBoxLayout *topLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->mOkButton = buttonBox->button(QDialogButtonBox::Ok); d->mOkButton->setObjectName(QStringLiteral("ok_button")); d->mOkButton->setDefault(true); d->mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &FollowUpReminderSelectDateDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &FollowUpReminderSelectDateDialog::reject); setModal(true); QWidget *mainWidget = new QWidget(this); topLayout->addWidget(mainWidget); topLayout->addWidget(buttonBox); QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget); mainLayout->setContentsMargins(0, 0, 0, 0); QFormLayout *formLayout = new QFormLayout; formLayout->setContentsMargins(0, 0, 0, 0); mainLayout->addLayout(formLayout); d->mDateComboBox = new KDateComboBox; QDate currentDate = QDate::currentDate().addDays(1); d->mDateComboBox->setMinimumDate(QDate::currentDate()); d->mDateComboBox->setObjectName(QStringLiteral("datecombobox")); d->mDateComboBox->setDate(currentDate); formLayout->addRow(i18n("Date:"), d->mDateComboBox); d->mCollectionCombobox = new Akonadi::CollectionComboBox(model); d->mCollectionCombobox->setMinimumWidth(250); d->mCollectionCombobox->setAccessRightsFilter(Akonadi::Collection::CanCreateItem); d->mCollectionCombobox->setMimeTypeFilter(QStringList() << KCalendarCore::Todo::todoMimeType()); d->mCollectionCombobox->setObjectName(QStringLiteral("collectioncombobox")); formLayout->addRow(i18n("Store ToDo in:"), d->mCollectionCombobox); connect(d->mDateComboBox->lineEdit(), &QLineEdit::textChanged, this, &FollowUpReminderSelectDateDialog::slotDateChanged); connect(d->mCollectionCombobox, qOverload(&Akonadi::CollectionComboBox::currentIndexChanged), this, &FollowUpReminderSelectDateDialog::updateOkButton); updateOkButton(); } FollowUpReminderSelectDateDialog::~FollowUpReminderSelectDateDialog() { delete d; } void FollowUpReminderSelectDateDialog::updateOkButton() { d->mOkButton->setEnabled(!d->mDateComboBox->lineEdit()->text().isEmpty() && d->mDateComboBox->date().isValid() && (d->mCollectionCombobox->count() > 0) && d->mCollectionCombobox->currentCollection().isValid()); } void FollowUpReminderSelectDateDialog::slotDateChanged() { updateOkButton(); } QDate FollowUpReminderSelectDateDialog::selectedDate() const { return d->mDateComboBox->date(); } Akonadi::Collection FollowUpReminderSelectDateDialog::collection() const { return d->mCollectionCombobox->currentCollection(); } void FollowUpReminderSelectDateDialog::accept() { const QDate date = selectedDate(); if (date < QDate::currentDate()) { KMessageBox::error(this, i18n("The selected date must be greater than the current date."), i18n("Invalid date")); return; } if (!d->mCollectionCombobox->currentCollection().isValid()) { KMessageBox::error(this, i18n("The selected folder is not valid."), i18n("Invalid folder")); return; } QDialog::accept(); } diff --git a/messagecomposer/src/helper/messagefactoryng.cpp b/messagecomposer/src/helper/messagefactoryng.cpp index 1d822b27..bb849e75 100644 --- a/messagecomposer/src/helper/messagefactoryng.cpp +++ b/messagecomposer/src/helper/messagefactoryng.cpp @@ -1,1014 +1,1013 @@ /* Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi Copyright (C) 2017-2019 Laurent Montel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagefactoryng.h" #include "settings/messagecomposersettings.h" #include "messagefactoryforwardjob.h" #include "messagefactoryreplyjob.h" #include "MessageComposer/Util" #include #ifndef QT_NO_CURSOR #include #endif #include #include #include #include #include #include #include #include "helper/messagehelper.h" #include #include "messagecomposer_debug.h" #include #include -#include using namespace MessageComposer; namespace KMime { namespace Types { static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right) { return left.addrSpec().asString() == right.addrSpec().asString(); } } } /** * Strips all the user's addresses from an address list. This is used * when replying. */ static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KIdentityManagement::IdentityManager *manager) { KMime::Types::Mailbox::List addresses(list); for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) { if (manager->thatIsMe(it->prettyAddress())) { it = addresses.erase(it); } else { ++it; } } return addresses; } MessageFactoryNG::MessageFactoryNG(const KMime::Message::Ptr &origMsg, Akonadi::Item::Id id, const Akonadi::Collection &col, QObject *parent) : QObject(parent) , m_identityManager(nullptr) , m_origMsg(origMsg) , m_folderId(0) , m_parentFolderId(0) , m_collection(col) , m_replyStrategy(MessageComposer::ReplySmart) , m_quote(true) , m_id(id) { } MessageFactoryNG::~MessageFactoryNG() { } // Return the addresses to use when replying to the author of msg. // See . static KMime::Types::Mailbox::List authorMailboxes( const KMime::Message::Ptr &msg, KMime::Types::Mailbox::List mailingLists) { if (auto mrt = msg->headerByType("Mail-Reply-To")) { return KMime::Types::Mailbox::listFrom7BitString(mrt->as7BitString(false)); } if (auto rt = msg->replyTo(false)) { // Did a mailing list munge Reply-To? auto mboxes = rt->mailboxes(); for (const auto &list : mailingLists) { mboxes.removeAll(list); } if (!mboxes.isEmpty()) { return mboxes; } } return msg->from(true)->mailboxes(); } void MessageFactoryNG::slotCreateReplyDone(const KMime::Message::Ptr &msg, bool replyAll) { applyCharset(msg); MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusReplied()); if (m_parentFolderId > 0) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Fcc"); header->fromUnicodeString(QString::number(m_parentFolderId), "utf-8"); msg->setHeader(header); } if (auto hrd = m_origMsg->headerByType("X-KMail-EncryptActionEnabled")) { if (hrd->as7BitString(false).contains("true")) { auto header = new KMime::Headers::Generic("X-KMail-EncryptActionEnabled"); header->fromUnicodeString(QStringLiteral("true"), "utf-8"); msg->setHeader(header); } } msg->assemble(); MessageReply reply; reply.msg = msg; reply.replyAll = replyAll; Q_EMIT createReplyDone(reply); } void MessageFactoryNG::createReplyAsync() { KMime::Message::Ptr msg(new KMime::Message); QByteArray refStr; bool replyAll = true; KMime::Types::Mailbox::List toList; KMime::Types::Mailbox::List replyToList; const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, originalIdentity); replyToList = m_origMsg->replyTo()->mailboxes(); msg->contentType()->setCharset("utf-8"); if (auto hdr = m_origMsg->headerByType("List-Post")) { const QString hdrListPost = hdr->asUnicodeString(); if (hdrListPost.contains(QLatin1String("mailto:"), Qt::CaseInsensitive)) { QRegExp rx(QStringLiteral("]+)@([^>]+)>"), Qt::CaseInsensitive); if (rx.indexIn(hdrListPost, 0) != -1) { // matched KMime::Types::Mailbox mailbox; mailbox.fromUnicodeString(rx.cap(1) + QLatin1Char('@') + rx.cap(2)); m_mailingListAddresses << mailbox; } } } switch (m_replyStrategy) { case MessageComposer::ReplySmart: { if (auto hdr = m_origMsg->headerByType("Mail-Followup-To")) { toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); } else if (!m_mailingListAddresses.isEmpty()) { if (replyToList.isEmpty()) { toList = (KMime::Types::Mailbox::List() << m_mailingListAddresses.at(0)); } else { toList = replyToList; } } else { // Doesn't seem to be a mailing list. auto originalFromList = m_origMsg->from()->mailboxes(); auto originalToList = m_origMsg->to()->mailboxes(); if (m_identityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList)) && !m_identityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList)) ) { // Sender seems to be one of our own identities and recipient is not, // so we assume that this is a reply to a "sent" mail where the user // wants to add additional information for the recipient. toList = originalToList; } else { // "Normal" case: reply to sender. toList = authorMailboxes(m_origMsg, m_mailingListAddresses); } replyAll = false; } // strip all my addresses from the list of recipients const KMime::Types::Mailbox::List recipients = toList; toList = stripMyAddressesFromAddressList(recipients, m_identityManager); // ... unless the list contains only my addresses (reply to self) if (toList.isEmpty() && !recipients.isEmpty()) { toList << recipients.first(); } break; } case MessageComposer::ReplyList: { if (auto hdr = m_origMsg->headerByType("Mail-Followup-To")) { KMime::Types::Mailbox mailbox; mailbox.from7BitString(hdr->as7BitString(false)); toList << mailbox; } else if (!m_mailingListAddresses.isEmpty()) { toList << m_mailingListAddresses[ 0 ]; } else if (!replyToList.isEmpty()) { // assume a Reply-To header mangling mailing list toList = replyToList; } // strip all my addresses from the list of recipients const KMime::Types::Mailbox::List recipients = toList; toList = stripMyAddressesFromAddressList(recipients, m_identityManager); break; } case MessageComposer::ReplyAll: if (auto hdr = m_origMsg->headerByType("Mail-Followup-To")) { toList = KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false)); } else { auto ccList = stripMyAddressesFromAddressList(m_origMsg->cc()->mailboxes(), m_identityManager); if (!m_mailingListAddresses.isEmpty()) { toList = stripMyAddressesFromAddressList(m_origMsg->to()->mailboxes(), m_identityManager); bool addMailingList = true; for (const KMime::Types::Mailbox &m : m_mailingListAddresses) { if (toList.contains(m)) { addMailingList = false; break; } } if (addMailingList) { toList += m_mailingListAddresses.front(); } ccList += authorMailboxes(m_origMsg, m_mailingListAddresses); } else { // Doesn't seem to be a mailing list. auto originalFromList = m_origMsg->from()->mailboxes(); auto originalToList = m_origMsg->to()->mailboxes(); if (m_identityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList)) && !m_identityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList)) ) { // Sender seems to be one of our own identities and recipient is not, // so we assume that this is a reply to a "sent" mail where the user // wants to add additional information for the recipient. toList = originalToList; } else { // "Normal" case: reply to sender. toList = stripMyAddressesFromAddressList(m_origMsg->to()->mailboxes(), m_identityManager); toList += authorMailboxes(m_origMsg, m_mailingListAddresses); } } for (const KMime::Types::Mailbox &mailbox : ccList) { msg->cc()->addAddress(mailbox); } } break; case MessageComposer::ReplyAuthor: toList = authorMailboxes(m_origMsg, m_mailingListAddresses); replyAll = false; break; case MessageComposer::ReplyNone: // the addressees will be set by the caller break; default: Q_UNREACHABLE(); } for (const KMime::Types::Mailbox &mailbox : qAsConst(toList)) { msg->to()->addAddress(mailbox); } refStr = getRefStr(m_origMsg); if (!refStr.isEmpty()) { msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8"); } //In-Reply-To = original msg-id msg->inReplyTo()->from7BitString(m_origMsg->messageID()->as7BitString(false)); msg->subject()->fromUnicodeString(MessageCore::StringUtil::replySubject(m_origMsg.data()), "utf-8"); // If the reply shouldn't be blank, apply the template to the message if (m_quote) { MessageFactoryReplyJob *job = new MessageFactoryReplyJob; connect(job, &MessageFactoryReplyJob::replyDone, this, &MessageFactoryNG::slotCreateReplyDone); job->setMsg(msg); job->setReplyAll(replyAll); job->setIdentityManager(m_identityManager); job->setSelection(m_selection); job->setTemplate(m_template); job->setOrigMsg(m_origMsg); job->setCollection(m_collection); job->start(); } else { slotCreateReplyDone(msg, replyAll); } } void MessageFactoryNG::slotCreateForwardDone(const KMime::Message::Ptr &msg) { applyCharset(msg); MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusForwarded()); msg->assemble(); Q_EMIT createForwardDone(msg); } void MessageFactoryNG::createForwardAsync() { KMime::Message::Ptr msg(new KMime::Message); // 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 (!m_origMsg->contentType()->isMultipart() && (!m_origMsg->contentType()->isText() || (m_origMsg->contentType()->isText() && m_origMsg->contentType()->subType() != "html" && m_origMsg->contentType()->subType() != "plain"))) { const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, originalIdentity); msg->removeHeader(); msg->removeHeader(); msg->contentType()->setMimeType("multipart/mixed"); //TODO: Andras: somebody should check if this is correct. :) // empty text part KMime::Content *msgPart = new KMime::Content; msgPart->contentType()->setMimeType("text/plain"); msg->addContent(msgPart); // the old contents of the mail KMime::Content *secondPart = new KMime::Content; secondPart->contentType()->setMimeType(m_origMsg->contentType()->mimeType()); secondPart->setBody(m_origMsg->body()); // use the headers of the original mail secondPart->setHead(m_origMsg->head()); msg->addContent(secondPart); msg->assemble(); } // 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 { //TODO Check if this is ok msg->setHead(m_origMsg->head()); msg->setBody(m_origMsg->body()); QString oldContentType = msg->contentType()->asUnicodeString(); const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, originalIdentity); // restore the content type, MessageHelper::initFromMessage() sets the contents type to // text/plain, via initHeader(), for unclear reasons msg->contentType()->fromUnicodeString(oldContentType, "utf-8"); msg->assemble(); } msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(m_origMsg.data()), "utf-8"); MessageFactoryForwardJob *job = new MessageFactoryForwardJob; connect(job, &MessageFactoryForwardJob::forwardDone, this, &MessageFactoryNG::slotCreateForwardDone); job->setIdentityManager(m_identityManager); job->setMsg(msg); job->setSelection(m_selection); job->setTemplate(m_template); job->setOrigMsg(m_origMsg); job->setCollection(m_collection); job->start(); } QPair< KMime::Message::Ptr, QList< KMime::Content * > > MessageFactoryNG::createAttachedForward(const Akonadi::Item::List &items) { // create forwarded message with original message as attachment // remove headers that shouldn't be forwarded KMime::Message::Ptr msg(new KMime::Message); QList< KMime::Content * > attachments; const int numberOfItems(items.count()); if (numberOfItems >= 2) { // don't respect X-KMail-Identity headers because they might differ for // the selected mails MessageHelper::initHeader(msg, m_identityManager, 0); } else if (numberOfItems == 1) { KMime::Message::Ptr firstMsg = MessageComposer::Util::message(items.first()); const uint originalIdentity = identityUoid(firstMsg); MessageHelper::initFromMessage(msg, firstMsg, m_identityManager, originalIdentity); msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(firstMsg.data()), "utf-8"); } MessageHelper::setAutomaticFields(msg, true); #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif if (numberOfItems == 0) { attachments << createForwardAttachmentMessage(m_origMsg); MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusForwarded()); } else { // iterate through all the messages to be forwarded attachments.reserve(items.count()); for (const Akonadi::Item &item : qAsConst(items)) { attachments << createForwardAttachmentMessage(MessageComposer::Util::message(item)); MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded()); } } applyCharset(msg); //msg->assemble(); return QPair< KMime::Message::Ptr, QList< KMime::Content * > >(msg, QList< KMime::Content * >() << attachments); } KMime::Content *MessageFactoryNG::createForwardAttachmentMessage(const KMime::Message::Ptr &fwdMsg) { // remove headers that shouldn't be forwarded MessageCore::StringUtil::removePrivateHeaderFields(fwdMsg); fwdMsg->removeHeader(); fwdMsg->assemble(); // set the part KMime::Content *msgPart = new KMime::Content(fwdMsg.data()); msgPart->contentType()->setMimeType("message/rfc822"); msgPart->contentDisposition()->setParameter(QStringLiteral("filename"), i18n("forwarded message")); msgPart->contentDisposition()->setDisposition(KMime::Headers::CDinline); msgPart->contentDescription()->fromUnicodeString(fwdMsg->from()->asUnicodeString() + QLatin1String(": ") + fwdMsg->subject()->asUnicodeString(), "utf-8"); msgPart->setBody(fwdMsg->encodedContent()); msgPart->assemble(); MessageComposer::Util::addLinkInformation(fwdMsg, 0, Akonadi::MessageStatus::statusForwarded()); return msgPart; } KMime::Message::Ptr MessageFactoryNG::createResend() { KMime::Message::Ptr msg(new KMime::Message); msg->setContent(m_origMsg->encodedContent()); msg->parse(); msg->removeHeader(); uint originalIdentity = identityUoid(m_origMsg); // Set the identity from above KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Identity"); header->fromUnicodeString(QString::number(originalIdentity), "utf-8"); msg->setHeader(header); // Restore the original bcc field as this is overwritten in applyIdentity msg->bcc(m_origMsg->bcc()); return msg; } KMime::Message::Ptr MessageFactoryNG::createRedirect(const QString &toStr, const QString &ccStr, const QString &bccStr, int transportId, const QString &fcc, int identity) { if (!m_origMsg) { return KMime::Message::Ptr(); } // copy the message 1:1 KMime::Message::Ptr msg(new KMime::Message); msg->setContent(m_origMsg->encodedContent()); msg->parse(); uint id = identity; if (identity == -1) { if (auto hrd = msg->headerByType("X-KMail-Identity")) { const QString strId = hrd->asUnicodeString().trimmed(); if (!strId.isEmpty()) { id = strId.toUInt(); } } } const KIdentityManagement::Identity &ident = m_identityManager->identityForUoidOrDefault(id); // X-KMail-Redirect-From: content const QString strByWayOf = QString::fromLocal8Bit("%1 (by way of %2 <%3>)") .arg(m_origMsg->from()->asUnicodeString(), ident.fullName(), ident.primaryEmailAddress()); // Resent-From: content const QString strFrom = QString::fromLocal8Bit("%1 <%2>") .arg(ident.fullName(), ident.primaryEmailAddress()); // format the current date to be used in Resent-Date: // FIXME: generate datetime the same way as KMime, otherwise we get inconsistency // in unit-tests. Unfortunatelly RFC2822Date is not enough for us, we need the // composition hack below const QDateTime dt = QDateTime::currentDateTime(); const QString newDate = QLocale::c().toString(dt, QStringLiteral("ddd, ")) +dt.toString(Qt::RFC2822Date); // Clean up any resent headers msg->removeHeader("Resent-Cc"); msg->removeHeader("Resent-Bcc"); msg->removeHeader("Resent-Sender"); // date, from to and id will be set anyway // prepend Resent-*: headers (c.f. RFC2822 3.6.6) QString msgIdSuffix; if (MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix()) { msgIdSuffix = MessageComposer::MessageComposerSettings::customMsgIDSuffix(); } KMime::Headers::Generic *header = new KMime::Headers::Generic("Resent-Message-ID"); header->fromUnicodeString(MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), msgIdSuffix), "utf-8"); msg->setHeader(header); header = new KMime::Headers::Generic("Resent-Date"); header->fromUnicodeString(newDate, "utf-8"); msg->setHeader(header); header = new KMime::Headers::Generic("Resent-From"); header->fromUnicodeString(strFrom, "utf-8"); msg->setHeader(header); if (msg->to(false)) { KMime::Headers::To *headerT = new KMime::Headers::To; headerT->fromUnicodeString(m_origMsg->to()->asUnicodeString(), "utf-8"); msg->setHeader(headerT); } header = new KMime::Headers::Generic("Resent-To"); header->fromUnicodeString(toStr, "utf-8"); msg->setHeader(header); if (!ccStr.isEmpty()) { header = new KMime::Headers::Generic("Resent-Cc"); header->fromUnicodeString(ccStr, "utf-8"); msg->setHeader(header); } if (!bccStr.isEmpty()) { header = new KMime::Headers::Generic("Resent-Bcc"); header->fromUnicodeString(bccStr, "utf-8"); msg->setHeader(header); } header = new KMime::Headers::Generic("X-KMail-Redirect-From"); header->fromUnicodeString(strByWayOf, "utf-8"); msg->setHeader(header); if (transportId != -1) { header = new KMime::Headers::Generic("X-KMail-Transport"); header->fromUnicodeString(QString::number(transportId), "utf-8"); msg->setHeader(header); } if (!fcc.isEmpty()) { header = new KMime::Headers::Generic("X-KMail-Fcc"); header->fromUnicodeString(fcc, "utf-8"); msg->setHeader(header); } const bool fccIsDisabled = ident.disabledFcc(); if (fccIsDisabled) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled"); header->fromUnicodeString(QStringLiteral("true"), "utf-8"); msg->setHeader(header); } else { msg->removeHeader("X-KMail-FccDisabled"); } msg->assemble(); MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusForwarded()); return msg; } KMime::Message::Ptr MessageFactoryNG::createDeliveryReceipt() { QString receiptTo; if (auto hrd = m_origMsg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return KMime::Message::Ptr(); } receiptTo.remove(QChar::fromLatin1('\n')); KMime::Message::Ptr receipt(new KMime::Message); const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(receipt, m_origMsg, m_identityManager, originalIdentity); receipt->to()->fromUnicodeString(receiptTo, QStringLiteral("utf-8").toLatin1()); receipt->subject()->fromUnicodeString(i18n("Receipt: ") + m_origMsg->subject()->asUnicodeString(), "utf-8"); QString str = QStringLiteral("Your message was successfully delivered."); str += QLatin1String("\n\n---------- Message header follows ----------\n"); str += QString::fromLatin1(m_origMsg->head()); str += QLatin1String("--------------------------------------------\n"); // Conversion to toLatin1 is correct here as Mail headers should contain // ascii only receipt->setBody(str.toLatin1()); MessageHelper::setAutomaticFields(receipt); receipt->assemble(); return receipt; } KMime::Message::Ptr MessageFactoryNG::createMDN(KMime::MDN::ActionMode a, KMime::MDN::DispositionType d, KMime::MDN::SendingMode s, int mdnQuoteOriginal, const QVector &m) { // extract where to send to: QString receiptTo; if (auto hrd = m_origMsg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return KMime::Message::Ptr(new KMime::Message); } receiptTo.remove(QChar::fromLatin1('\n')); QString special; // fill in case of error, warning or failure // extract where to send from: QString finalRecipient = m_identityManager->identityForUoidOrDefault(identityUoid(m_origMsg)).fullEmailAddr(); // // Generate message: // KMime::Message::Ptr receipt(new KMime::Message()); const uint originalIdentity = identityUoid(m_origMsg); MessageHelper::initFromMessage(receipt, m_origMsg, m_identityManager, originalIdentity); receipt->contentType()->from7BitString("multipart/report"); receipt->contentType()->setBoundary(KMime::multiPartBoundary()); receipt->contentType()->setCharset("us-ascii"); receipt->removeHeader(); // Modify the ContentType directly (replaces setAutomaticFields(true)) receipt->contentType()->setParameter(QStringLiteral("report-type"), QStringLiteral("disposition-notification")); QString description = replaceHeadersInString(m_origMsg, KMime::MDN::descriptionFor(d, m)); // text/plain part: KMime::Content *firstMsgPart = new KMime::Content(m_origMsg.data()); firstMsgPart->contentType()->setMimeType("text/plain"); firstMsgPart->contentType()->setCharset("utf-8"); firstMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); firstMsgPart->setBody(description.toUtf8()); receipt->addContent(firstMsgPart); // message/disposition-notification part: KMime::Content *secondMsgPart = new KMime::Content(m_origMsg.data()); secondMsgPart->contentType()->setMimeType("message/disposition-notification"); secondMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); QByteArray originalRecipient = ""; if (auto hrd = m_origMsg->headerByType("Original-Recipient")) { originalRecipient = hrd->as7BitString(false); } secondMsgPart->setBody(KMime::MDN::dispositionNotificationBodyContent( finalRecipient, originalRecipient, m_origMsg->messageID()->as7BitString(false), /* Message-ID */ d, a, s, m, special)); receipt->addContent(secondMsgPart); if (mdnQuoteOriginal < 0 || mdnQuoteOriginal > 2) { mdnQuoteOriginal = 0; } /* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/ KMime::Content *thirdMsgPart = new KMime::Content(m_origMsg.data()); switch (mdnQuoteOriginal) { case 1: thirdMsgPart->contentType()->setMimeType("message/rfc822"); thirdMsgPart->setBody(MessageCore::StringUtil::asSendableString(m_origMsg)); receipt->addContent(thirdMsgPart); break; case 2: thirdMsgPart->contentType()->setMimeType("text/rfc822-headers"); thirdMsgPart->setBody(MessageCore::StringUtil::headerAsSendableString(m_origMsg)); receipt->addContent(thirdMsgPart); break; case 0: default: delete thirdMsgPart; break; } receipt->to()->fromUnicodeString(receiptTo, "utf-8"); //Laurent: We don't translate subject ? receipt->subject()->from7BitString("Message Disposition Notification"); KMime::Headers::InReplyTo *header = new KMime::Headers::InReplyTo; header->fromUnicodeString(m_origMsg->messageID()->asUnicodeString(), "utf-8"); receipt->setHeader(header); receipt->references()->from7BitString(getRefStr(m_origMsg)); receipt->assemble(); qCDebug(MESSAGECOMPOSER_LOG) << "final message:" + receipt->encodedContent(); receipt->assemble(); return receipt; } QPair< KMime::Message::Ptr, KMime::Content * > MessageFactoryNG::createForwardDigestMIME(const Akonadi::Item::List &items) { KMime::Message::Ptr msg(new KMime::Message); KMime::Content *digest = new KMime::Content(msg.data()); QString mainPartText = i18n("\nThis is a MIME digest forward. The content of the" " message is contained in the attachment(s).\n\n\n"); digest->contentType()->setMimeType("multipart/digest"); digest->contentType()->setBoundary(KMime::multiPartBoundary()); digest->contentDescription()->fromUnicodeString(QStringLiteral("Digest of %1 messages.").arg(items.count()), "utf8"); digest->contentDisposition()->setFilename(QStringLiteral("digest")); digest->fromUnicodeString(mainPartText); int id = 0; for (const Akonadi::Item &item : qAsConst(items)) { KMime::Message::Ptr fMsg = MessageComposer::Util::message(item); if (id == 0) { if (auto hrd = fMsg->headerByType("X-KMail-Identity")) { id = hrd->asUnicodeString().toInt(); } } MessageCore::StringUtil::removePrivateHeaderFields(fMsg); fMsg->removeHeader(); fMsg->assemble(); KMime::Content *part = new KMime::Content(digest); part->contentType()->setMimeType("message/rfc822"); part->contentType()->setCharset(fMsg->contentType()->charset()); part->contentID()->setIdentifier(fMsg->contentID()->identifier()); part->contentDescription()->fromUnicodeString(fMsg->contentDescription()->asUnicodeString(), "utf8"); part->contentDisposition()->setParameter(QStringLiteral("name"), i18n("forwarded message")); part->fromUnicodeString(QString::fromLatin1(fMsg->encodedContent())); part->assemble(); MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded()); digest->addContent(part); } digest->assemble(); id = m_folderId; MessageHelper::initHeader(msg, m_identityManager, id); // qCDebug(MESSAGECOMPOSER_LOG) << "digest:" << digest->contents().size() << digest->encodedContent(); return QPair< KMime::Message::Ptr, KMime::Content * >(msg, digest); } void MessageFactoryNG::setIdentityManager(KIdentityManagement::IdentityManager *ident) { m_identityManager = ident; } void MessageFactoryNG::setReplyStrategy(MessageComposer::ReplyStrategy replyStrategy) { m_replyStrategy = replyStrategy; } void MessageFactoryNG::setSelection(const QString &selection) { m_selection = selection; } void MessageFactoryNG::setQuote(bool quote) { m_quote = quote; } void MessageFactoryNG::setTemplate(const QString &templ) { m_template = templ; } void MessageFactoryNG::setMailingListAddresses(const KMime::Types::Mailbox::List &listAddresses) { m_mailingListAddresses << listAddresses; } void MessageFactoryNG::setFolderIdentity(Akonadi::Collection::Id folderIdentityId) { m_folderId = folderIdentityId; } void MessageFactoryNG::putRepliesInSameFolder(Akonadi::Collection::Id parentColId) { m_parentFolderId = parentColId; } bool MessageFactoryNG::MDNRequested(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); return !receiptTo.isEmpty(); } bool MessageFactoryNG::MDNConfirmMultipleRecipients(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); // 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. qCDebug(MESSAGECOMPOSER_LOG) << "KEmailAddress::splitAddressList(receiptTo):" << KEmailAddress::splitAddressList(receiptTo).join(QLatin1Char('\n')); return KEmailAddress::splitAddressList(receiptTo).count() > 1; } bool MessageFactoryNG::MDNReturnPathEmpty(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); // 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 [...] KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, "Return-Path"); QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain; qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath; return returnPath.isEmpty(); } bool MessageFactoryNG::MDNReturnPathNotInRecieptTo(const KMime::Message::Ptr &msg) { // extract where to send to: QString receiptTo; if (auto hrd = msg->headerByType("Disposition-Notification-To")) { receiptTo = hrd->asUnicodeString(); } if (receiptTo.trimmed().isEmpty()) { return false; } receiptTo.remove(QChar::fromLatin1('\n')); // 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 [...] KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, QStringLiteral("Return-Path").toLatin1()); QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain; qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath; return !receiptTo.contains(returnPath, Qt::CaseSensitive); } bool MessageFactoryNG::MDNMDNUnknownOption(const KMime::Message::Ptr &msg) { // 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; if (auto hrd = msg->headerByType("Disposition-Notification-Options")) { notificationOptions = hrd->asUnicodeString(); } if (notificationOptions.contains(QLatin1String("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: return true; } return false; } uint MessageFactoryNG::identityUoid(const KMime::Message::Ptr &msg) { QString idString; if (auto hdr = msg->headerByType("X-KMail-Identity")) { idString = hdr->asUnicodeString().trimmed(); } bool ok = false; uint id = idString.toUInt(&ok); if (!ok || id == 0) { id = m_identityManager->identityForAddress(msg->to()->asUnicodeString() + QLatin1String(", ") + msg->cc()->asUnicodeString()).uoid(); } if (id == 0 && m_folderId > 0) { id = m_folderId; } return id; } QString MessageFactoryNG::replaceHeadersInString(const KMime::Message::Ptr &msg, const QString &s) { QString result = s; QRegExp rx(QStringLiteral("\\$\\{([a-z0-9-]+)\\}"), Qt::CaseInsensitive); Q_ASSERT(rx.isValid()); QRegExp rxDate(QStringLiteral("\\$\\{date\\}")); Q_ASSERT(rxDate.isValid()); const QString sDate = KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch()); qCDebug(MESSAGECOMPOSER_LOG) << "creating mdn date:" << msg->date()->dateTime().toSecsSinceEpoch() << sDate; 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) { const QByteArray ba = rx.cap(1).toLatin1(); QString replacement; if (auto hrd = msg->headerByType(ba.constData())) { replacement = hrd->asUnicodeString(); } result.replace(idx, rx.matchedLength(), replacement); idx += replacement.length(); } return result; } void MessageFactoryNG::applyCharset(const KMime::Message::Ptr msg) { if (MessageComposer::MessageComposerSettings::forceReplyCharset()) { // 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(m_origMsg->contentType()->charset()); QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset())); if (!codec) { qCCritical(MESSAGECOMPOSER_LOG) << "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 = MessageComposer::MessageComposerSettings::preferredCharsets(); QVector chars; chars.reserve(charsets.count()); for (const QString &charset : charsets) { chars << charset.toLatin1(); } QByteArray fallbackCharset = MessageComposer::Util::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)); } } } QByteArray MessageFactoryNG::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; } diff --git a/messagecomposer/src/recipient/recipientspicker.cpp b/messagecomposer/src/recipient/recipientspicker.cpp index 9d46c134..eb4c68fa 100644 --- a/messagecomposer/src/recipient/recipientspicker.cpp +++ b/messagecomposer/src/recipient/recipientspicker.cpp @@ -1,236 +1,235 @@ /* Copyright (c) 2010 Volker Krause This file was part of KMail. Copyright (c) 2005 Cornelius Schumacher 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 "recipientspicker.h" #include "settings/messagecomposersettings.h" #include #include #include #include #include #include #include #include #include #include "messagecomposer_debug.h" #include #include #include #include #include -#include using namespace MessageComposer; RecipientsPicker::RecipientsPicker(QWidget *parent) : QDialog(parent) { setObjectName(QStringLiteral("RecipientsPicker")); setWindowTitle(i18n("Select Recipient")); QVBoxLayout *mainLayout = new QVBoxLayout(this); mView = new Akonadi::RecipientsPickerWidget(false, nullptr, this); mainLayout->addWidget(mView); mainLayout->setStretchFactor(mView, 1); connect(mView->view()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RecipientsPicker::slotSelectionChanged); connect(mView->view(), &QAbstractItemView::doubleClicked, this, &RecipientsPicker::slotPicked); QPushButton *searchLDAPButton = new QPushButton(i18n("Search &Directory Service"), this); connect(searchLDAPButton, &QPushButton::clicked, this, &RecipientsPicker::slotSearchLDAP); mainLayout->addWidget(searchLDAPButton); KConfig config(QStringLiteral("kabldaprc")); KConfigGroup group = config.group("LDAP"); int numHosts = group.readEntry("NumSelectedHosts", 0); if (!numHosts) { searchLDAPButton->setVisible(false); } QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this); mUser1Button = new QPushButton; buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole); mUser2Button = new QPushButton; buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole); mUser3Button = new QPushButton; buttonBox->addButton(mUser3Button, QDialogButtonBox::ActionRole); mUser4Button = new QPushButton; buttonBox->addButton(mUser4Button, QDialogButtonBox::ActionRole); connect(buttonBox, &QDialogButtonBox::rejected, this, &RecipientsPicker::reject); mainLayout->addWidget(buttonBox); mUser4Button->setText(i18n("Add as &Reply-To")); mUser3Button->setText(i18n("Add as &To")); mUser2Button->setText(i18n("Add as CC")); mUser1Button->setText(i18n("Add as &BCC")); connect(mUser1Button, &QPushButton::clicked, this, &RecipientsPicker::slotBccClicked); connect(mUser2Button, &QPushButton::clicked, this, &RecipientsPicker::slotCcClicked); connect(mUser3Button, &QPushButton::clicked, this, &RecipientsPicker::slotToClicked); connect(mUser4Button, &QPushButton::clicked, this, &RecipientsPicker::slotReplyToClicked); mView->emailAddressSelectionWidget()->searchLineEdit()->setFocus(); readConfig(); slotSelectionChanged(); } RecipientsPicker::~RecipientsPicker() { writeConfig(); } void RecipientsPicker::slotSelectionChanged() { const bool hasSelection = !mView->emailAddressSelectionWidget()->selectedAddresses().isEmpty(); mUser1Button->setEnabled(hasSelection); mUser2Button->setEnabled(hasSelection); mUser3Button->setEnabled(hasSelection); mUser4Button->setEnabled(hasSelection); } void RecipientsPicker::setRecipients(const Recipient::List &) { mView->view()->selectionModel()->clear(); } void RecipientsPicker::setDefaultType(Recipient::Type type) { mDefaultType = type; mUser1Button->setDefault(type == Recipient::To); mUser2Button->setDefault(type == Recipient::Cc); mUser3Button->setDefault(type == Recipient::Bcc); mUser4Button->setDefault(type == Recipient::ReplyTo); } void RecipientsPicker::slotToClicked() { pick(Recipient::To); } void RecipientsPicker::slotReplyToClicked() { pick(Recipient::ReplyTo); } void RecipientsPicker::slotCcClicked() { pick(Recipient::Cc); } void RecipientsPicker::slotBccClicked() { pick(Recipient::Bcc); } void RecipientsPicker::slotPicked() { pick(mDefaultType); } void RecipientsPicker::pick(Recipient::Type type) { qCDebug(MESSAGECOMPOSER_LOG) << int(type); const Akonadi::EmailAddressSelection::List selections = mView->emailAddressSelectionWidget()->selectedAddresses(); const int count = selections.count(); if (count == 0) { return; } if (count > MessageComposerSettings::self()->maximumRecipients()) { KMessageBox::sorry(this, i18np("You selected 1 recipient. The maximum supported number of " "recipients is %2. Please adapt the selection.", "You selected %1 recipients. The maximum supported number of " "recipients is %2. Please adapt the selection.", count, MessageComposerSettings::self()->maximumRecipients())); return; } bool tooManyAddress = false; for (const Akonadi::EmailAddressSelection &selection : selections) { Recipient recipient; recipient.setType(type); recipient.setEmail(selection.quotedEmail()); Q_EMIT pickedRecipient(recipient, tooManyAddress); if (tooManyAddress) { break; } } } void RecipientsPicker::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { close(); } QDialog::keyPressEvent(event); } void RecipientsPicker::readConfig() { KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); KConfigGroup group(cfg, "RecipientsPicker"); QSize size = group.readEntry("Size", QSize()); if (!size.isEmpty()) { resize(size); } } void RecipientsPicker::writeConfig() { KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); KConfigGroup group(cfg, "RecipientsPicker"); group.writeEntry("Size", size()); } void RecipientsPicker::slotSearchLDAP() { if (!mLdapSearchDialog) { mLdapSearchDialog = new KLDAP::LdapSearchDialog(this); connect(mLdapSearchDialog, &KLDAP::LdapSearchDialog::contactsAdded, this, &RecipientsPicker::ldapSearchResult); } mLdapSearchDialog->setSearchText(mView->emailAddressSelectionWidget()->searchLineEdit()->text()); mLdapSearchDialog->show(); } void RecipientsPicker::ldapSearchResult() { const KContacts::Addressee::List contacts = mLdapSearchDialog->selectedContacts(); for (const KContacts::Addressee &contact : contacts) { bool tooManyAddress = false; Q_EMIT pickedRecipient(Recipient(contact.fullEmail(), Recipient::Undefined), tooManyAddress); if (tooManyAddress) { break; } } } diff --git a/mimetreeparser/autotests/basicobjecttreeparsertest.h b/mimetreeparser/autotests/basicobjecttreeparsertest.h index 12426f55..f24cc2fd 100644 --- a/mimetreeparser/autotests/basicobjecttreeparsertest.h +++ b/mimetreeparser/autotests/basicobjecttreeparsertest.h @@ -1,56 +1,56 @@ /* Copyright (c) 2010 Thomas McGuire Copyright (c) 2019 Sandro Knauß 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. */ -#ifndef MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H -#define MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H +#ifndef MIMETREEPARSER_TESTS_BASICOBJECTTREEPARSERTEST_H +#define MIMETREEPARSER_TESTS_BASICOBJECTTREEPARSERTEST_H #include class ObjectTreeParserTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testMailWithoutEncryption(); void testSMIMESignedEncrypted(); void testOpenPGPSignedEncrypted(); void testOpenPGPEncryptedAndSigned(); void testForwardedOpenPGPSignedEncrypted(); void testSignedForwardedOpenPGPSignedEncrypted(); void testOpenPGPEncrypted(); void testOpenPGPEncryptedNotDecrypted(); void testAsync_data(); void testAsync(); void testHtmlContent_data(); void testHtmlContent(); void testMemoryHole(); void testRenderedTree(); void testRenderedTree_data(); void testParsePlainMessage(); void testParseEncapsulatedMessage(); void testMissingContentTypeHeader(); void testInlinePGPDecryption(); void testInlinePGPSigned(); void testHTML(); void testHTMLasText(); void testHTMLOnly(); }; -#endif // MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H +#endif // MIMETREEPARSER_TESTS_BASICOBJECTTREEPARSERTEST_H diff --git a/mimetreeparser/src/nodehelper.cpp b/mimetreeparser/src/nodehelper.cpp index 136cc624..141ce430 100644 --- a/mimetreeparser/src/nodehelper.cpp +++ b/mimetreeparser/src/nodehelper.cpp @@ -1,1093 +1,1092 @@ /* Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nodehelper.h" #include "mimetreeparser_debug.h" #include "partmetadata.h" #include "messagepart.h" #include "interfaces/bodypart.h" #include "temporaryfile/attachmenttemporaryfilesdirs.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include namespace MimeTreeParser { NodeHelper::NodeHelper() : mAttachmentFilesDir(new AttachmentTemporaryFilesDirs()) { //TODO(Andras) add methods to modify these prefixes mLocalCodec = QTextCodec::codecForLocale(); // In the case of Japan. Japanese locale name is "eucjp" but // The Japanese mail systems normally used "iso-2022-jp" of locale name. // We want to change locale name from eucjp to iso-2022-jp at KMail only. // (Introduction to i18n, 6.6 Limit of Locale technology): // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP // is the standard for Internet, and Shift-JIS is the encoding // for Windows and Macintosh. if (mLocalCodec) { const QByteArray codecNameLower = mLocalCodec->name().toLower(); if (codecNameLower == "eucjp" #if defined Q_OS_WIN || defined Q_OS_MACX || codecNameLower == "shift-jis" // OK? #endif ) { mLocalCodec = QTextCodec::codecForName("jis7"); // QTextCodec *cdc = QTextCodec::codecForName("jis7"); // QTextCodec::setCodecForLocale(cdc); // KLocale::global()->setEncoding(cdc->mibEnum()); } } } NodeHelper::~NodeHelper() { if (mAttachmentFilesDir) { mAttachmentFilesDir->forceCleanTempFiles(); delete mAttachmentFilesDir; mAttachmentFilesDir = nullptr; } clear(); } void NodeHelper::setNodeProcessed(KMime::Content *node, bool recurse) { if (!node) { return; } mProcessedNodes.append(node); qCDebug(MIMETREEPARSER_LOG) << "Node processed: " << node->index().toString() << node->contentType()->as7BitString(); //<< " decodedContent" << node->decodedContent(); if (recurse) { const auto contents = node->contents(); for (KMime::Content *c : contents) { setNodeProcessed(c, true); } } } void NodeHelper::setNodeUnprocessed(KMime::Content *node, bool recurse) { if (!node) { return; } mProcessedNodes.removeAll(node); //avoid double addition of extra nodes, eg. encrypted attachments const QMap >::iterator it = mExtraContents.find(node); if (it != mExtraContents.end()) { Q_FOREACH (KMime::Content *c, it.value()) { KMime::Content *p = c->parent(); if (p) { p->removeContent(c); } } qDeleteAll(it.value()); qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key(); mExtraContents.erase(it); } qCDebug(MIMETREEPARSER_LOG) << "Node UNprocessed: " << node; if (recurse) { const auto contents = node->contents(); for (KMime::Content *c : contents) { setNodeUnprocessed(c, true); } } } bool NodeHelper::nodeProcessed(KMime::Content *node) const { if (!node) { return true; } return mProcessedNodes.contains(node); } static void clearBodyPartMemento(QMap &bodyPartMementoMap) { for (QMap::iterator it = bodyPartMementoMap.begin(), end = bodyPartMementoMap.end(); it != end; ++it) { Interface::BodyPartMemento *memento = it.value(); memento->detach(); delete memento; } bodyPartMementoMap.clear(); } void NodeHelper::clear() { mProcessedNodes.clear(); mEncryptionState.clear(); mSignatureState.clear(); mOverrideCodecs.clear(); std::for_each(mBodyPartMementoMap.begin(), mBodyPartMementoMap.end(), &clearBodyPartMemento); mBodyPartMementoMap.clear(); QMap >::ConstIterator end(mExtraContents.constEnd()); for (QMap >::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) { Q_FOREACH (KMime::Content *c, it.value()) { KMime::Content *p = c->parent(); if (p) { p->removeContent(c); } } qDeleteAll(it.value()); qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key(); } mExtraContents.clear(); mDisplayEmbeddedNodes.clear(); mDisplayHiddenNodes.clear(); } void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state) { mEncryptionState[node] = state; } KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const { return mEncryptionState.value(node, KMMsgNotEncrypted); } void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state) { mSignatureState[node] = state; } KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const { return mSignatureState.value(node, KMMsgNotSigned); } PartMetaData NodeHelper::partMetaData(KMime::Content *node) { return mPartMetaDatas.value(node, PartMetaData()); } void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData) { mPartMetaDatas.insert(node, metaData); } QString NodeHelper::writeFileToTempFile(KMime::Content *node, const QString &filename) { QString fname = createTempDir(persistentIndex(node)); if (fname.isEmpty()) { return QString(); } fname += QLatin1Char('/') + filename; QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString(); mAttachmentFilesDir->addTempFile(fname); return QString(); } f.write(QByteArray()); mAttachmentFilesDir->addTempFile(fname); // make file read-only so that nobody gets the impression that he might // edit attached files (cf. bug #52813) f.setPermissions(QFileDevice::ReadUser); f.close(); return fname; } QString NodeHelper::writeNodeToTempFile(KMime::Content *node) { // If the message part is already written to a file, no point in doing it again. // This function is called twice actually, once from the rendering of the attachment // in the body and once for the header. QUrl existingFileName = tempFileUrlFromNode(node); if (!existingFileName.isEmpty()) { return existingFileName.toLocalFile(); } QString fname = createTempDir(persistentIndex(node)); if (fname.isEmpty()) { return QString(); } QString fileName = NodeHelper::fileName(node); // strip off a leading path int slashPos = fileName.lastIndexOf(QLatin1Char('/')); if (-1 != slashPos) { fileName = fileName.mid(slashPos + 1); } if (fileName.isEmpty()) { fileName = QStringLiteral("unnamed"); } fname += QLatin1Char('/') + fileName; qCDebug(MIMETREEPARSER_LOG) << "Create temp file: " << fname; QByteArray data = node->decodedContent(); if (node->contentType()->isText() && !data.isEmpty()) { // convert CRLF to LF before writing text attachments to disk data = KMime::CRLFtoLF(data); } QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString(); mAttachmentFilesDir->addTempFile(fname); return QString(); } f.write(data); mAttachmentFilesDir->addTempFile(fname); // make file read-only so that nobody gets the impression that he might // edit attached files (cf. bug #52813) f.setPermissions(QFileDevice::ReadUser); f.close(); return fname; } QUrl NodeHelper::tempFileUrlFromNode(const KMime::Content *node) { if (!node) { return QUrl(); } const QString index = persistentIndex(node); foreach (const QString &path, mAttachmentFilesDir->temporaryFiles()) { const int right = path.lastIndexOf(QLatin1Char('/')); int left = path.lastIndexOf(QLatin1String(".index."), right); if (left != -1) { left += 7; } QStringRef storedIndex(&path, left, right - left); if (left != -1 && storedIndex == index) { return QUrl::fromLocalFile(path); } } return QUrl(); } QString NodeHelper::createTempDir(const QString ¶m) { QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/messageviewer_XXXXXX") + QLatin1String(".index.") + param); tempFile->open(); const QString fname = tempFile->fileName(); delete tempFile; QFile fFile(fname); if (!(fFile.permissions() & QFileDevice::WriteUser)) { // Not there or not writable if (!QDir().mkpath(fname) || !fFile.setPermissions(QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser)) { mAttachmentFilesDir->addTempDir(fname); return QString(); //failed create } } Q_ASSERT(!fname.isNull()); mAttachmentFilesDir->addTempDir(fname); return fname; } void NodeHelper::forceCleanTempFiles() { mAttachmentFilesDir->forceCleanTempFiles(); delete mAttachmentFilesDir; mAttachmentFilesDir = nullptr; } void NodeHelper::removeTempFiles() { //Don't delete as it will be deleted in class mAttachmentFilesDir->removeTempFiles(); mAttachmentFilesDir = new AttachmentTemporaryFilesDirs(); } void NodeHelper::addTempFile(const QString &file) { mAttachmentFilesDir->addTempFile(file); } bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node) { const KMime::Content *const topLevel = node->topLevel(); const KMime::Content *cur = node; while (cur && cur != topLevel) { const bool parentIsMessage = cur->parent() && cur->parent()->contentType(false) && cur->parent()->contentType()->mimeType().toLower() == "message/rfc822"; if (parentIsMessage && cur->parent() != topLevel) { return true; } cur = cur->parent(); } return false; } QByteArray NodeHelper::charset(KMime::Content *node) { if (node->contentType(false)) { return node->contentType(false)->charset(); } else { return node->defaultCharset(); } } KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const { KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown; if (!node) { return myState; } KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(const_cast(node)); if (i < 0) { return myState; } for (; i < contents.size(); ++i) { auto next = contents.at(i); KMMsgEncryptionState otherState = encryptionState(next); // NOTE: children are tested ONLY when parent is not encrypted if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) { otherState = overallEncryptionState(next->contents().at(0)); } if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) { otherState = overallEncryptionState(extraContents(next).at(0)); } if (next == node) { myState = otherState; } switch (otherState) { case KMMsgEncryptionStateUnknown: break; case KMMsgNotEncrypted: if (myState == KMMsgFullyEncrypted) { myState = KMMsgPartiallyEncrypted; } else if (myState != KMMsgPartiallyEncrypted) { myState = KMMsgNotEncrypted; } break; case KMMsgPartiallyEncrypted: myState = KMMsgPartiallyEncrypted; break; case KMMsgFullyEncrypted: if (myState != KMMsgFullyEncrypted) { myState = KMMsgPartiallyEncrypted; } break; case KMMsgEncryptionProblematic: break; } } qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgEncryptionState:" << myState; return myState; } KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const { KMMsgSignatureState myState = KMMsgSignatureStateUnknown; if (!node) { return myState; } KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(const_cast(node)); if (i < 0) { //Be safe return myState; } for (; i < contents.size(); ++i) { auto next = contents.at(i); KMMsgSignatureState otherState = signatureState(next); // NOTE: children are tested ONLY when parent is not encrypted if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) { otherState = overallSignatureState(next->contents().at(0)); } if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) { otherState = overallSignatureState(extraContents(next).at(0)); } if (next == node) { myState = otherState; } switch (otherState) { case KMMsgSignatureStateUnknown: break; case KMMsgNotSigned: if (myState == KMMsgFullySigned) { myState = KMMsgPartiallySigned; } else if (myState != KMMsgPartiallySigned) { myState = KMMsgNotSigned; } break; case KMMsgPartiallySigned: myState = KMMsgPartiallySigned; break; case KMMsgFullySigned: if (myState != KMMsgFullySigned) { myState = KMMsgPartiallySigned; } break; case KMMsgSignatureProblematic: break; } } qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgSignatureState:" << myState; return myState; } void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode) { const QByteArray body = aAutoDecode ? node->decodedContent() : node->body(); QMimeDatabase db; QMimeType mime = db.mimeTypeForData(body); QString mimetype = mime.name(); node->contentType()->setMimeType(mimetype.toLatin1()); } bool NodeHelper::hasMailHeader(const char *header, const KMime::Content *message) const { return message->hasHeader(header); } KMime::Headers::Base const *NodeHelper::mailHeaderAsBase(const char *header, const KMime::Content *message) const { if (mHeaderOverwrite.contains(message)) { foreach (const auto messagePart, mHeaderOverwrite.value(message)) { if (messagePart->hasHeader(header)) { return messagePart->header(header); // Found. } } } return message->headerByType(header); } KMime::Headers::Generics::AddressList const *NodeHelper::mailHeaderAsAddressList(const char *header, const KMime::Content *message) const { /* works without this is maybe faster ? if(strcmp(header, "to") == 0) { return message->to(); } else if(strcmp(header, "replyTo") == 0) { return message->replyTo(); } else if(strcmp(header, "bcc") == 0) { return message->bcc(); } else if(strcmp(header, "cc") == 0) { return message->cc(); } */ auto addressList = new KMime::Headers::Generics::AddressList(); const auto hrd = mailHeaderAsBase(header, message); const QByteArray &data = hrd->as7BitString(false); addressList->from7BitString(data); return addressList; } void NodeHelper::clearOverrideHeaders() { mHeaderOverwrite.clear(); } void NodeHelper::registerOverrideHeader(KMime::Content *message, MessagePart::Ptr part) { if (!mHeaderOverwrite.contains(message)) { mHeaderOverwrite[message] = QVector(); } mHeaderOverwrite[message].append(part); } QDateTime NodeHelper::dateHeader(KMime::Content *message) const { const auto dateHeader = mailHeaderAsBase("date", message); if (dateHeader != nullptr) { return static_cast(dateHeader)->dateTime(); } return QDateTime(); } void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec) { if (!node) { return; } mOverrideCodecs[node] = codec; } const QTextCodec *NodeHelper::codec(KMime::Content *node) { if (!node) { return mLocalCodec; } const QTextCodec *c = mOverrideCodecs.value(node, nullptr); if (!c) { // no override-codec set for this message, try the CT charset parameter: QByteArray charset = node->contentType()->charset(); // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients. if (charset.toLower() == "us-ascii") { charset = "utf-8"; } c = codecForName(charset); } if (!c) { // no charset means us-ascii (RFC 2045), so using local encoding should // be okay c = mLocalCodec; } return c; } const QTextCodec *NodeHelper::codecForName(const QByteArray &_str) { if (_str.isEmpty()) { return nullptr; } QByteArray codec = _str.toLower(); return KCharsets::charsets()->codecForName(QLatin1String(codec)); } QString NodeHelper::fileName(const KMime::Content *node) { QString name = const_cast(node)->contentDisposition()->filename(); if (name.isEmpty()) { name = const_cast(node)->contentType()->name(); } name = name.trimmed(); return name; } //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node, const QByteArray &which) const { const QMap< QString, QMap >::const_iterator nit = mBodyPartMementoMap.find(persistentIndex(node)); if (nit == mBodyPartMementoMap.end()) { return nullptr; } const QMap::const_iterator it = nit->find(which.toLower()); return it != nit->end() ? it.value() : nullptr; } //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento) { QMap &mementos = mBodyPartMementoMap[persistentIndex(node)]; const QByteArray whichLower = which.toLower(); const QMap::iterator it = mementos.lowerBound(whichLower); if (it != mementos.end() && it.key() == whichLower) { delete it.value(); if (memento) { it.value() = memento; } else { mementos.erase(it); } } else { mementos.insert(whichLower, memento); } } bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const { qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node); return mDisplayEmbeddedNodes.contains(node); } void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded) { qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded; if (displayedEmbedded) { mDisplayEmbeddedNodes.insert(node); } else { mDisplayEmbeddedNodes.remove(node); } } bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const { return mDisplayHiddenNodes.contains(node); } void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden) { if (displayedHidden) { mDisplayHiddenNodes.insert(node); } else { mDisplayEmbeddedNodes.remove(node); } } /*! Creates a persistent index string that bridges the gap between the permanent nodes and the temporary ones. Used internally for robust indexing. */ QString NodeHelper::persistentIndex(const KMime::Content *node) const { if (!node) { return QString(); } QString indexStr = node->index().toString(); if (indexStr.isEmpty()) { QMapIterator > it(mExtraContents); while (it.hasNext()) { it.next(); const auto &extraNodes = it.value(); for (int i = 0; i < extraNodes.size(); i++) { if (extraNodes[i] == node) { indexStr = QStringLiteral("e%1").arg(i); const QString parentIndex = persistentIndex(it.key()); if (!parentIndex.isEmpty()) { indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr); } return indexStr; } } } } else { const KMime::Content *const topLevel = node->topLevel(); //if the node is an extra node, prepend the index of the extra node to the url QMapIterator > it(mExtraContents); while (it.hasNext()) { it.next(); const QList &extraNodes = extraContents(it.key()); for (int i = 0; i < extraNodes.size(); ++i) { KMime::Content *const extraNode = extraNodes[i]; if (topLevel == extraNode) { indexStr.prepend(QStringLiteral("e%1:").arg(i)); const QString parentIndex = persistentIndex(it.key()); if (!parentIndex.isEmpty()) { indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr); } return indexStr; } } } } return indexStr; } KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const { KMime::Content *c = node->topLevel(); if (c) { const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), QString::SkipEmptyParts); const int pathPartsSize(pathParts.size()); for (int i = 0; i < pathPartsSize; ++i) { const QString &path = pathParts[i]; if (path.startsWith(QLatin1Char('e'))) { const QList &extraParts = mExtraContents.value(c); const int idx = path.midRef(1, -1).toInt(); c = (idx < extraParts.size()) ? extraParts[idx] : nullptr; } else { c = c->content(KMime::ContentIndex(path)); } if (!c) { break; } } } return c; } QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const { return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place); } KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const { if (url.isEmpty()) { return mMessage.data(); } if (!url.isLocalFile()) { return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path()); } else { const QString path = url.toLocalFile(); // extract from //qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2" // start of the index is something that is not a number followed by a dot: \D. // index is only made of numbers,"." and ":": ([0-9.:]+) // index is the last part of the folder name: / const QRegExp rIndex(QStringLiteral("\\D\\.([e0-9.:]+)/")); //search the occurrence at most at the end if (rIndex.lastIndexIn(path) != -1) { return contentFromIndex(mMessage.data(), rIndex.cap(1)); } return mMessage.data(); } } QString NodeHelper::fixEncoding(const QString &encoding) { QString returnEncoding = encoding; - // According to http://www.iana.org/assignments/character-sets, uppercase is + // According to https://www.iana.org/assignments/character-sets, uppercase is // preferred in MIME headers const QString returnEncodingToUpper = returnEncoding.toUpper(); if (returnEncodingToUpper.contains(QLatin1String("ISO "))) { returnEncoding = returnEncodingToUpper; returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-")); } return returnEncoding; } //----------------------------------------------------------------------------- QString NodeHelper::encodingForName(const QString &descriptiveName) { QString encoding = KCharsets::charsets()->encodingForName(descriptiveName); return NodeHelper::fixEncoding(encoding); } QStringList NodeHelper::supportedEncodings(bool usAscii) { QStringList encodingNames = KCharsets::charsets()->availableEncodingNames(); QStringList encodings; QMap mimeNames; QStringList::ConstIterator constEnd(encodingNames.constEnd()); for (QStringList::ConstIterator it = encodingNames.constBegin(); it != constEnd; ++it) { QTextCodec *codec = KCharsets::charsets()->codecForName(*it); QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it); if (!mimeNames.contains(mimeName)) { encodings.append(KCharsets::charsets()->descriptionForEncoding(*it)); mimeNames.insert(mimeName, true); } } encodings.sort(); if (usAscii) { encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii"))); } return encodings; } QString NodeHelper::fromAsString(KMime::Content *node) const { if (auto topLevel = dynamic_cast(node->topLevel())) { return topLevel->from()->asUnicodeString(); } else { auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(), [node](const QList &nodes) { return nodes.contains(node); }); if (realNode != mExtraContents.cend()) { return fromAsString(realNode.key()); } } return QString(); } void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content) { qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content; mExtraContents[topLevelNode].append(content); } void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode) { qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode; mExtraContents[topLevelNode].clear(); } QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const { return mExtraContents.value(topLevelnode); } void NodeHelper::mergeExtraNodes(KMime::Content *node) { if (!node) { return; } const QList extraNodes = extraContents(node); for (KMime::Content *extra : extraNodes) { if (node->bodyIsMessage()) { qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node <encodedContent() << "\n====== with =======\n" << extra << extra->encodedContent(); continue; } KMime::Content *c = new KMime::Content(node); c->setContent(extra->encodedContent()); c->parse(); node->addContent(c); } Q_FOREACH (KMime::Content *child, node->contents()) { mergeExtraNodes(child); } } void NodeHelper::cleanFromExtraNodes(KMime::Content *node) { if (!node) { return; } const QList extraNodes = extraContents(node); for (KMime::Content *extra : extraNodes) { QByteArray s = extra->encodedContent(); const auto children = node->contents(); for (KMime::Content *c : children) { if (c->encodedContent() == s) { node->removeContent(c); } } } Q_FOREACH (KMime::Content *child, node->contents()) { cleanFromExtraNodes(child); } } KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode) { /*The merge is done in several steps: 1) merge the extra nodes into topLevelNode 2) copy the modified (merged) node tree into a new node tree 3) restore the original node tree in topLevelNode by removing the extra nodes from it The reason is that extra nodes are assigned by pointer value to the nodes in the original tree. */ if (!topLevelNode) { return nullptr; } mergeExtraNodes(topLevelNode); KMime::Message *m = new KMime::Message; m->setContent(topLevelNode->encodedContent()); m->parse(); cleanFromExtraNodes(topLevelNode); // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent(); // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent(); return m; } KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const { const QList xc = extraContents(content); if (!xc.empty()) { if (xc.size() == 1) { return xc.front(); } else { qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?"; } } return nullptr; } bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel) { bool returnValue = false; if (node) { KMime::Content *curNode = node; KMime::Content *decryptedNode = nullptr; const QByteArray type = node->contentType(false) ? QByteArray(node->contentType()->mediaType()).toLower() : "text"; const QByteArray subType = node->contentType(false) ? node->contentType()->subType().toLower() : "plain"; const bool isMultipart = node->contentType(false) && node->contentType()->isMultipart(); bool isSignature = false; qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType; if (isMultipart) { if (subType == "signed") { isSignature = true; } else if (subType == "encrypted") { decryptedNode = decryptedNodeForContent(curNode); } } else if (type == "application") { if (subType == "octet-stream") { decryptedNode = decryptedNodeForContent(curNode); } else if (subType == "pkcs7-signature") { isSignature = true; } else if (subType == "pkcs7-mime") { // note: subtype pkcs7-mime can also be signed // and we do NOT want to remove the signature! if (encryptionState(curNode) != KMMsgNotEncrypted) { decryptedNode = decryptedNodeForContent(curNode); } } } if (decryptedNode) { qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header " "and then processing the children."; Q_ASSERT(addHeaders); KMime::Content headers; headers.setHead(curNode->head()); headers.parse(); if (decryptedNode->contentType(false)) { headers.contentType()->from7BitString(decryptedNode->contentType()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentTransferEncoding(false)) { headers.contentTransferEncoding()->from7BitString(decryptedNode->contentTransferEncoding()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentDisposition(false)) { headers.contentDisposition()->from7BitString(decryptedNode->contentDisposition()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentDescription(false)) { headers.contentDescription()->from7BitString(decryptedNode->contentDescription()->as7BitString(false)); } else { headers.removeHeader(); } headers.assemble(); resultingData += headers.head() + '\n'; unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1); returnValue = true; } else if (isSignature) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is."; // We can't change the nodes under the signature, as that would invalidate it. Add the signature // and its child as-is if (addHeaders) { resultingData += curNode->head() + '\n'; } resultingData += curNode->encodedBody(); returnValue = false; } else if (isMultipart) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children."; // Normal multipart node, add the header and all of its children bool somethingChanged = false; if (addHeaders) { resultingData += curNode->head() + '\n'; } const QByteArray boundary = curNode->contentType()->boundary(); foreach (KMime::Content *child, curNode->contents()) { resultingData += "\n--" + boundary + '\n'; const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1); if (changed) { somethingChanged = true; } } resultingData += "\n--" + boundary + "--\n\n"; returnValue = somethingChanged; } else if (curNode->bodyIsMessage()) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child."; if (addHeaders) { resultingData += curNode->head() + '\n'; } returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1); } else { qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is."; if (addHeaders) { resultingData += curNode->head() + '\n'; } resultingData += curNode->body(); returnValue = false; } } qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done."; return returnValue; } KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage) { QByteArray resultingData; const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true); if (messageChanged) { #if 0 qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData; QFile bla("stripped.mbox"); bla.open(QIODevice::WriteOnly); bla.write(resultingData); bla.close(); #endif KMime::Message::Ptr newMessage(new KMime::Message); newMessage->setContent(resultingData); newMessage->parse(); return newMessage; } else { return KMime::Message::Ptr(); } } QVector NodeHelper::attachmentsOfExtraContents() const { QVector result; for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) { foreach (auto content, it.value()) { if (KMime::isAttachment(content)) { result.push_back(content); } else { result += content->attachments(); } } } return result; } } diff --git a/templateparser/autotests/templateextracthtmlelementfrommailtest.h b/templateparser/autotests/templateextracthtmlelementfrommailtest.h index 0313b70e..8688e8d6 100644 --- a/templateparser/autotests/templateextracthtmlelementfrommailtest.h +++ b/templateparser/autotests/templateextracthtmlelementfrommailtest.h @@ -1,37 +1,37 @@ /* Copyright (C) 2017-2019 Laurent Montel 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. */ -#ifndef TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEWTEST_H -#define TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEWTEST_H +#ifndef TEMPLATEEXTRACTHTMLELEMENTFROMMAILTEST_H +#define TEMPLATEEXTRACTHTMLELEMENTFROMMAILTEST_H #include class TemplateExtractHtmlElementFromMailTest : public QObject { Q_OBJECT public: explicit TemplateExtractHtmlElementFromMailTest(QObject *parent = nullptr); ~TemplateExtractHtmlElementFromMailTest() = default; private Q_SLOTS: void shouldHaveDefaultValue(); void shouldExtractHtml_data(); void shouldExtractHtml(); }; -#endif // TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEWTEST_H +#endif // TEMPLATEEXTRACTHTMLELEMENTFROMMAILTEST_H diff --git a/templateparser/autotests/templateextracttextfrommailtest.h b/templateparser/autotests/templateextracttextfrommailtest.h index bcad38cb..4f3a6131 100644 --- a/templateparser/autotests/templateextracttextfrommailtest.h +++ b/templateparser/autotests/templateextracttextfrommailtest.h @@ -1,36 +1,36 @@ /* Copyright (C) 2017-2019 Laurent Montel 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. */ -#ifndef TEMPLATEWEBENGINEVIEWTEST_H -#define TEMPLATEWEBENGINEVIEWTEST_H +#ifndef TEMPLATEEXTRACTTEXTFROMMAILTEST_H +#define TEMPLATEEXTRACTTEXTFROMMAILTEST_H #include class TemplateExtractTextFromMailTest : public QObject { Q_OBJECT public: explicit TemplateExtractTextFromMailTest(QObject *parent = nullptr); ~TemplateExtractTextFromMailTest(); private Q_SLOTS: void shouldHaveDefaultValue(); void shouldExtractHtml(); }; -#endif // TEMPLATEWEBENGINEVIEWTEST_H +#endif // TEMPLATEEXTRACTTEXTFROMMAILTEST_H diff --git a/templateparser/src/templateextracthtmlelementfrommail.h b/templateparser/src/templateextracthtmlelementfrommail.h index 1e84588b..3c6eaaf0 100644 --- a/templateparser/src/templateextracthtmlelementfrommail.h +++ b/templateparser/src/templateextracthtmlelementfrommail.h @@ -1,58 +1,58 @@ /* Copyright (C) 2017-2019 Laurent Montel 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. */ -#ifndef TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEW_H -#define TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEW_H +#ifndef TEMPLATEEXTRACTHTMLELEMENTFROMMAIL_H +#define TEMPLATEEXTRACTHTMLELEMENTFROMMAIL_H #include "templateparser_private_export.h" #include #include namespace TemplateParser { class TemplateWebEnginePage; class TEMPLATEPARSER_TESTS_EXPORT TemplateExtractHtmlElementFromMail : public QObject { Q_OBJECT public: explicit TemplateExtractHtmlElementFromMail(QObject *parent = nullptr); ~TemplateExtractHtmlElementFromMail(); Q_REQUIRED_RESULT QString bodyElement() const; Q_REQUIRED_RESULT QString headerElement() const; Q_REQUIRED_RESULT QString htmlElement() const; void setHtmlContent(const QString &html); Q_SIGNALS: void loadContentDone(bool success); private: void clear(); void slotLoadFinished(bool success); void handleHtmlInfo(const QVariant &result); QString mBodyElement; QString mHeaderElement; QString mHtmlElement; TemplateWebEnginePage *mPage = nullptr; }; } -#endif // TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEW_H +#endif // TEMPLATEEXTRACTHTMLELEMENTFROMMAIL_H diff --git a/templateparser/src/templateextracttextfrommail.h b/templateparser/src/templateextracttextfrommail.h index 24a2924d..6eedbc3f 100644 --- a/templateparser/src/templateextracttextfrommail.h +++ b/templateparser/src/templateextracttextfrommail.h @@ -1,53 +1,53 @@ /* Copyright (C) 2017-2019 Laurent Montel 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. */ -#ifndef TEMPLATEWEBENGINEVIEW_H -#define TEMPLATEWEBENGINEVIEW_H +#ifndef TEMPLATEEXTRACTTEXTFROMMAIL_H +#define TEMPLATEEXTRACTTEXTFROMMAIL_H #include "templateparser_export.h" #include namespace TemplateParser { class TemplateWebEnginePage; /** * @brief The TemplateExtractTextFromMail class * @author Laurent Montel */ class TEMPLATEPARSER_EXPORT TemplateExtractTextFromMail : public QObject { Q_OBJECT public: explicit TemplateExtractTextFromMail(QObject *parent = nullptr); ~TemplateExtractTextFromMail(); void setHtmlContent(const QString &html); Q_REQUIRED_RESULT QString plainText() const; Q_SIGNALS: void loadContentDone(bool success); private: void slotLoadFinished(bool ok); void setPlainText(const QString &plainText); QString mExtractedPlainText; TemplateWebEnginePage *mPage = nullptr; }; } -#endif // TEMPLATEWEBENGINEVIEW_H +#endif // TEMPLATEEXTRACTTEXTFROMMAIL_H diff --git a/templateparser/src/templateparserjob.cpp b/templateparser/src/templateparserjob.cpp index 228bc1a6..eab14f96 100644 --- a/templateparser/src/templateparserjob.cpp +++ b/templateparser/src/templateparserjob.cpp @@ -1,1570 +1,1570 @@ /* Copyright (C) 2017-2019 Laurent Montel 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 "templateparserjob.h" #include "templateparserjob_p.h" #include "templateparserextracthtmlinfo.h" #include "globalsettings_templateparser.h" #include "customtemplates_kfg.h" #include "templatesconfiguration_kfg.h" #include "templatesconfiguration.h" #include "templatesutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "templateparser_debug.h" #include #include #include #include #include #include namespace { Q_DECL_CONSTEXPR inline int pipeTimeout() { return 15 * 1000; } static QTextCodec *selectCharset(const QStringList &charsets, const QString &text) { for (const QString &name : charsets) { // We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because // the former knows us-ascii is latin1. bool ok = true; QTextCodec *codec = nullptr; if (name == QLatin1String("locale")) { codec = QTextCodec::codecForLocale(); } else { codec = KCharsets::charsets()->codecForName(name, ok); } if (!ok || !codec) { qCWarning(TEMPLATEPARSER_LOG) << "Could not get text codec for charset" << name; continue; } if (codec->canEncode(text)) { // Special check for us-ascii (needed because us-ascii is not exactly latin1). if (name == QLatin1String("us-ascii") && !KMime::isUsAscii(text)) { continue; } qCDebug(TEMPLATEPARSER_LOG) << "Chosen charset" << name << codec->name(); return codec; } } qCDebug(TEMPLATEPARSER_LOG) << "No appropriate charset found."; return KCharsets::charsets()->codecForName(QStringLiteral("utf-8")); } } using namespace TemplateParser; TemplateParserJobPrivate::TemplateParserJobPrivate(const KMime::Message::Ptr &amsg, const TemplateParserJob::Mode amode) : mMsg(amsg) , mMode(amode) { mEmptySource = new MimeTreeParser::SimpleObjectTreeSource; mEmptySource->setDecryptMessage(mAllowDecryption); mOtp = new MimeTreeParser::ObjectTreeParser(mEmptySource); mOtp->setAllowAsync(false); } TemplateParserJobPrivate::~TemplateParserJobPrivate() { delete mEmptySource; delete mOtp; } void TemplateParserJobPrivate::setAllowDecryption(const bool allowDecryption) { mAllowDecryption = allowDecryption; mEmptySource->setDecryptMessage(mAllowDecryption); } TemplateParserJob::TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode, QObject *parent) : QObject(parent) , d(new TemplateParserJobPrivate(amsg, amode)) { } TemplateParserJob::~TemplateParserJob() = default; void TemplateParserJob::setSelection(const QString &selection) { d->mSelection = selection; } void TemplateParserJob::setAllowDecryption(const bool allowDecryption) { d->setAllowDecryption(allowDecryption); } bool TemplateParserJob::shouldStripSignature() const { // Only strip the signature when replying, it should be preserved when forwarding return (d->mMode == Reply || d->mMode == ReplyAll) && TemplateParserSettings::self()->stripSignature(); } void TemplateParserJob::setIdentityManager(KIdentityManagement::IdentityManager *ident) { d->m_identityManager = ident; } void TemplateParserJob::setCharsets(const QStringList &charsets) { d->mCharsets = charsets; } int TemplateParserJob::parseQuotes(const QString &prefix, const QString &str, QString "e) { int pos = prefix.length(); int len; const int str_len = str.length(); // Also allow the german lower double-quote sign as quote separator, not only // the standard ASCII quote ("). This fixes bug 166728. const QList< QChar > quoteChars = {QLatin1Char('"'), 0x201C}; QChar prev(QChar::Null); pos++; len = pos; while (pos < str_len) { const QChar c = str[pos]; pos++; len++; if (!prev.isNull()) { quote.append(c); prev = QChar::Null; } else { if (c == QLatin1Char('\\')) { prev = c; } else if (quoteChars.contains(c)) { break; } else { quote.append(c); } } } return len; } void TemplateParserJob::process(const KMime::Message::Ptr &aorig_msg, qint64 afolder) { if (aorig_msg == nullptr) { qCDebug(TEMPLATEPARSER_LOG) << "aorig_msg == 0!"; Q_EMIT parsingDone(d->mForceCursorPosition); deleteLater(); return; } d->mOrigMsg = aorig_msg; d->mFolder = afolder; const QString tmpl = findTemplate(); if (tmpl.isEmpty()) { Q_EMIT parsingDone(d->mForceCursorPosition); deleteLater(); return; } processWithTemplate(tmpl); } void TemplateParserJob::process(const QString &tmplName, const KMime::Message::Ptr &aorig_msg, qint64 afolder) { d->mForceCursorPosition = false; d->mOrigMsg = aorig_msg; d->mFolder = afolder; const QString tmpl = findCustomTemplate(tmplName); processWithTemplate(tmpl); } void TemplateParserJob::processWithIdentity(uint uoid, const KMime::Message::Ptr &aorig_msg, qint64 afolder) { d->mIdentity = uoid; process(aorig_msg, afolder); } MimeTreeParser::MessagePart::Ptr toplevelTextNode(MimeTreeParser::MessagePart::Ptr messageTree) { foreach (const auto &mp, messageTree->subParts()) { auto text = mp.dynamicCast(); const auto attach = mp.dynamicCast(); if (text && !attach) { // TextMessagePart can have several subparts cause of PGP inline, we search for the first part with content foreach (const auto &sub, mp->subParts()) { if (!sub->text().trimmed().isEmpty()) { return sub; } } return text; } else if (const auto html = mp.dynamicCast()) { return html; } else if (const auto alternative = mp.dynamicCast()) { return alternative; } else { auto ret = toplevelTextNode(mp); if (ret) { return ret; } } } return MimeTreeParser::MessagePart::Ptr(); } void TemplateParserJob::processWithTemplate(const QString &tmpl) { d->mOtp->parseObjectTree(d->mOrigMsg.data()); const auto mp = toplevelTextNode(d->mOtp->parsedPart()); QString plainText = mp->plaintextContent(); QString htmlElement; if (mp->isHtml()) { htmlElement = d->mOtp->htmlContent(); if (plainText.isEmpty()) { //HTML-only mails plainText = htmlElement; } } else { //plain mails only QString htmlReplace = plainText.toHtmlEscaped(); htmlReplace.replace(QLatin1Char('\n'), QStringLiteral("
")); htmlElement = QStringLiteral("%1\n").arg(htmlReplace); } TemplateParserExtractHtmlInfo *job = new TemplateParserExtractHtmlInfo(this); connect(job, &TemplateParserExtractHtmlInfo::finished, this, &TemplateParserJob::slotExtractInfoDone); job->setHtmlForExtractingTextPlain(plainText); job->setTemplate(tmpl); job->setHtmlForExtractionHeaderAndBody(htmlElement); job->start(); } void TemplateParserJob::slotExtractInfoDone(const TemplateParserExtractHtmlInfoResult &result) { d->mExtractHtmlInfoResult = result; const QString tmpl = d->mExtractHtmlInfoResult.mTemplate; const int tmpl_len = tmpl.length(); QString plainBody, htmlBody; bool dnl = false; auto definedLocale = QLocale(); for (int i = 0; i < tmpl_len; ++i) { QChar c = tmpl[i]; // qCDebug(TEMPLATEPARSER_LOG) << "Next char: " << c; if (c == QLatin1Char('%')) { const QString cmd = tmpl.mid(i + 1); if (cmd.startsWith(QLatin1Char('-'))) { // dnl qCDebug(TEMPLATEPARSER_LOG) << "Command: -"; dnl = true; i += 1; } else if (cmd.startsWith(QLatin1String("REM="))) { // comments qCDebug(TEMPLATEPARSER_LOG) << "Command: REM="; QString q; const int len = parseQuotes(QStringLiteral("REM="), cmd, q); i += len; } else if (cmd.startsWith(QLatin1String("LANGUAGE="))) { QString q; const int len = parseQuotes(QStringLiteral("LANGUAGE="), cmd, q); i += len; if (!q.isEmpty()) { definedLocale = QLocale(q); } } else if (cmd.startsWith(QLatin1String("DICTIONARYLANGUAGE="))) { QString q; const int len = parseQuotes(QStringLiteral("DICTIONARYLANGUAGE="), cmd, q); i += len; if (!q.isEmpty()) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Dictionary"); header->fromUnicodeString(q, "utf-8"); d->mMsg->setHeader(header); } } else if (cmd.startsWith(QLatin1String("INSERT=")) || cmd.startsWith(QLatin1String("PUT="))) { QString q; int len = 0; if (cmd.startsWith(QLatin1String("INSERT="))) { // insert content of specified file as is qCDebug(TEMPLATEPARSER_LOG) << "Command: INSERT="; len = parseQuotes(QStringLiteral("INSERT="), cmd, q); } else { // insert content of specified file as is qCDebug(TEMPLATEPARSER_LOG) << "Command: PUT="; len = parseQuotes(QStringLiteral("PUT="), cmd, q); } i += len; QString path = KShell::tildeExpand(q); QFileInfo finfo(path); if (finfo.isRelative()) { path = QDir::homePath() + QLatin1Char('/') + q; } QFile file(path); if (file.open(QIODevice::ReadOnly)) { const QByteArray content = file.readAll(); const QString str = QString::fromLocal8Bit(content.constData(), content.size()); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (d->mDebug) { KMessageBox::error( nullptr, i18nc("@info", "Cannot insert content from file %1: %2", path, file.errorString())); } } else if (cmd.startsWith(QLatin1String("SYSTEM="))) { // insert content of specified file as is qCDebug(TEMPLATEPARSER_LOG) << "Command: SYSTEM="; QString q; const int len = parseQuotes(QStringLiteral("SYSTEM="), cmd, q); i += len; const QString pipe_cmd = q; const QString str = pipe(pipe_cmd, QString()); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("QUOTEPIPE="))) { // pipe message body through command and insert it as quotation qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTEPIPE="; QString q; const int len = parseQuotes(QStringLiteral("QUOTEPIPE="), cmd, q); i += len; const QString pipe_cmd = q; if (d->mOrigMsg) { const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed)); QString plainQuote = quotedPlainText(plainStr); if (plainQuote.endsWith(QLatin1Char('\n'))) { plainQuote.chop(1); } plainBody.append(plainQuote); const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed)); const QString htmlQuote = quotedHtmlText(htmlStr); htmlBody.append(htmlQuote); } } else if (cmd.startsWith(QLatin1String("QUOTE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTE"; i += strlen("QUOTE"); if (d->mOrigMsg) { QString plainQuote = quotedPlainText(plainMessageText(shouldStripSignature(), SelectionAllowed)); if (plainQuote.endsWith(QLatin1Char('\n'))) { plainQuote.chop(1); } plainBody.append(plainQuote); const QString htmlQuote = quotedHtmlText(htmlMessageText(shouldStripSignature(), SelectionAllowed)); htmlBody.append(htmlQuote); } } else if (cmd.startsWith(QLatin1String("FORCEDPLAIN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDPLAIN"; d->mQuotes = ReplyAsPlain; i += strlen("FORCEDPLAIN"); } else if (cmd.startsWith(QLatin1String("FORCEDHTML"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDHTML"; d->mQuotes = ReplyAsHtml; i += strlen("FORCEDHTML"); } else if (cmd.startsWith(QLatin1String("QHEADERS"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: QHEADERS"; i += strlen("QHEADERS"); if (d->mOrigMsg) { QString plainQuote = quotedPlainText(QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg))); if (plainQuote.endsWith(QLatin1Char('\n'))) { plainQuote.chop(1); } plainBody.append(plainQuote); const QString htmlQuote = quotedHtmlText(QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg))); const QString str = plainToHtml(htmlQuote); htmlBody.append(str); } } else if (cmd.startsWith(QLatin1String("HEADERS"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADERS"; i += strlen("HEADERS"); if (d->mOrigMsg) { const QString str = QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg)); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("TEXTPIPE="))) { // pipe message body through command and insert it as is qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXTPIPE="; QString q; const int len = parseQuotes(QStringLiteral("TEXTPIPE="), cmd, q); i += len; const QString pipe_cmd = q; if (d->mOrigMsg) { const QString plainStr = pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed)); plainBody.append(plainStr); const QString htmlStr = pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed)); htmlBody.append(htmlStr); } } else if (cmd.startsWith(QLatin1String("MSGPIPE="))) { // pipe full message through command and insert result as is qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGPIPE="; QString q; const int len = parseQuotes(QStringLiteral("MSGPIPE="), cmd, q); i += len; if (d->mOrigMsg) { QString pipe_cmd = q; const QString str = pipe(pipe_cmd, QString::fromLatin1(d->mOrigMsg->encodedContent())); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("BODYPIPE="))) { // pipe message body generated so far through command and insert result as is qCDebug(TEMPLATEPARSER_LOG) << "Command: BODYPIPE="; QString q; const int len = parseQuotes(QStringLiteral("BODYPIPE="), cmd, q); i += len; const QString pipe_cmd = q; const QString plainStr = pipe(pipe_cmd, plainBody); plainBody.append(plainStr); const QString htmlStr = pipe(pipe_cmd, htmlBody); const QString body = plainToHtml(htmlStr); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CLEARPIPE="))) { // pipe message body generated so far through command and // insert result as is replacing current body qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEARPIPE="; QString q; const int len = parseQuotes(QStringLiteral("CLEARPIPE="), cmd, q); i += len; const QString pipe_cmd = q; const QString plainStr = pipe(pipe_cmd, plainBody); plainBody = plainStr; const QString htmlStr = pipe(pipe_cmd, htmlBody); htmlBody = htmlStr; KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos"); header->fromUnicodeString(QString::number(0), "utf-8"); d->mMsg->setHeader(header); } else if (cmd.startsWith(QLatin1String("TEXT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXT"; i += strlen("TEXT"); if (d->mOrigMsg) { const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed); plainBody.append(plainStr); const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed); htmlBody.append(htmlStr); } } else if (cmd.startsWith(QLatin1String("OTEXTSIZE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXTSIZE"; i += strlen("OTEXTSIZE"); if (d->mOrigMsg) { const QString str = QStringLiteral("%1").arg(d->mOrigMsg->body().length()); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTEXT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXT"; i += strlen("OTEXT"); if (d->mOrigMsg) { const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed); plainBody.append(plainStr); const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed); htmlBody.append(htmlStr); } } else if (cmd.startsWith(QLatin1String("OADDRESSEESADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OADDRESSEESADDR"; i += strlen("OADDRESSEESADDR"); if (d->mOrigMsg) { const QString to = d->mOrigMsg->to()->asUnicodeString(); const QString cc = d->mOrigMsg->cc()->asUnicodeString(); if (!to.isEmpty()) { const QString toLine = i18nc("@item:intext email To", "To:") + QLatin1Char(' ') + to; plainBody.append(toLine); const QString body = plainToHtml(toLine); htmlBody.append(body); } if (!to.isEmpty() && !cc.isEmpty()) { plainBody.append(QLatin1Char('\n')); const QString str = plainToHtml(QString(QLatin1Char('\n'))); htmlBody.append(str); } if (!cc.isEmpty()) { const QString ccLine = i18nc("@item:intext email CC", "CC:") + QLatin1Char(' ') + cc; plainBody.append(ccLine); const QString str = plainToHtml(ccLine); htmlBody.append(str); } } } else if (cmd.startsWith(QLatin1String("CCADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCADDR"; i += strlen("CCADDR"); const QString str = d->mMsg->cc()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CCNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCNAME"; i += strlen("CCNAME"); const QString str = d->mMsg->cc()->displayString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CCFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCFNAME"; i += strlen("CCFNAME"); const QString str = d->mMsg->cc()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainToHtml(firstNameFromEmail); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("CCLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: CCLNAME"; i += strlen("CCLNAME"); const QString str = d->mMsg->cc()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOADDR"; i += strlen("TOADDR"); const QString str = d->mMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TONAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TONAME"; i += strlen("TONAME"); const QString str = (d->mMsg->to()->displayString()); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOFNAME"; i += strlen("TOFNAME"); const QString str = d->mMsg->to()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainToHtml(firstNameFromEmail); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLNAME"; i += strlen("TOLNAME"); const QString str = d->mMsg->to()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TOLIST"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLIST"; i += strlen("TOLIST"); const QString str = d->mMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMADDR"; i += strlen("FROMADDR"); const QString str = d->mMsg->from()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMNAME"; i += strlen("FROMNAME"); const QString str = d->mMsg->from()->displayString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMFNAME"; i += strlen("FROMFNAME"); const QString str = d->mMsg->from()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainToHtml(firstNameFromEmail); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FROMLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMLNAME"; i += strlen("FROMLNAME"); const QString str = d->mMsg->from()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("FULLSUBJECT")) || cmd.startsWith(QLatin1String("FULLSUBJ"))) { if (cmd.startsWith(QLatin1String("FULLSUBJ"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJ"; i += strlen("FULLSUBJ"); } else { qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJECT"; i += strlen("FULLSUBJECT"); } const QString str = d->mMsg->subject()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("MSGID"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGID"; i += strlen("MSGID"); const QString str = d->mMsg->messageID()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("OHEADER="))) { // insert specified content of header from original message qCDebug(TEMPLATEPARSER_LOG) << "Command: OHEADER="; QString q; const int len = parseQuotes(QStringLiteral("OHEADER="), cmd, q); i += len; if (d->mOrigMsg) { const QString hdr = q; QString str; if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) { str = hrdMsgOrigin->asUnicodeString(); } plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("HEADER="))) { // insert specified content of header from current message qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER="; QString q; const int len = parseQuotes(QStringLiteral("HEADER="), cmd, q); i += len; const QString hdr = q; QString str; if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) { str = hrdMsgOrigin->asUnicodeString(); } plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("HEADER( "))) { // insert specified content of header from current message qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER("; QRegularExpressionMatch match; const int res = cmd.indexOf(QRegularExpression(QLatin1String("^HEADER\\((.+)\\)")), 0, &match); if (res != 0) { // something wrong i += strlen("HEADER( "); } else { - i += match.capturedLength(0); //lenght of HEADER( + ) + i += match.capturedLength(0); //length of HEADER( + ) const QString hdr = match.captured(1).trimmed(); QString str; if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) { str = hrdMsgOrigin->asUnicodeString(); } plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCADDR"; i += strlen("OCCADDR"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCNAME"; i += strlen("OCCNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->displayString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCFNAME"; i += strlen("OCCFNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainToHtml(firstNameFromEmail); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OCCLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCLNAME"; i += strlen("OCCLNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->cc()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOADDR"; i += strlen("OTOADDR"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTONAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTONAME"; i += strlen("OTONAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->displayString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOFNAME"; i += strlen("OTOFNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainToHtml(firstNameFromEmail); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLNAME"; i += strlen("OTOLNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTOLIST"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLIST"; i += strlen("OTOLIST"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTO"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTO"; i += strlen("OTO"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->to()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMADDR"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMADDR"; i += strlen("OFROMADDR"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMNAME"; i += strlen("OFROMNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->displayString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMFNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMFNAME"; i += strlen("OFROMFNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->displayString(); const QString firstNameFromEmail = TemplateParser::Util::getFirstNameFromEmail(str); plainBody.append(firstNameFromEmail); const QString body = plainToHtml(firstNameFromEmail); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFROMLNAME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMLNAME"; i += strlen("OFROMLNAME"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->from()->displayString(); plainBody.append(TemplateParser::Util::getLastNameFromEmail(str)); const QString body = plainToHtml(TemplateParser::Util::getLastNameFromEmail(str)); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OFULLSUBJECT")) || cmd.startsWith(QLatin1String("OFULLSUBJ"))) { if (cmd.startsWith(QLatin1String("OFULLSUBJECT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJECT"; i += strlen("OFULLSUBJECT"); } else { qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJ"; i += strlen("OFULLSUBJ"); } if (d->mOrigMsg) { const QString str = d->mOrigMsg->subject()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OMSGID"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OMSGID"; i += strlen("OMSGID"); if (d->mOrigMsg) { const QString str = d->mOrigMsg->messageID()->asUnicodeString(); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("DATEEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DATEEN"; i += strlen("DATEEN"); const QDateTime date = QDateTime::currentDateTime(); QLocale locale(QLocale::C); const QString str = locale.toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("DATESHORT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DATESHORT"; i += strlen("DATESHORT"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("DATE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DATE"; i += strlen("DATE"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("DOW"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: DOW"; i += strlen("DOW"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TIMELONGEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONGEN"; i += strlen("TIMELONGEN"); const QDateTime date = QDateTime::currentDateTime(); QLocale locale(QLocale::C); const QString str = locale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TIMELONG"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONG"; i += strlen("TIMELONG"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("TIME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: TIME"; i += strlen("TIME"); const QDateTime date = QDateTime::currentDateTime(); const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } else if (cmd.startsWith(QLatin1String("ODATEEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATEEN"; i += strlen("ODATEEN"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = QLocale::c().toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("ODATESHORT"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATESHORT"; i += strlen("ODATESHORT"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("ODATE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATE"; i += strlen("ODATE"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.date(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("ODOW"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: ODOW"; i += strlen("ODOW"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTIMELONGEN"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONGEN"; i += strlen("OTIMELONGEN"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); QLocale locale(QLocale::C); const QString str = locale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTIMELONG"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONG"; i += strlen("OTIMELONG"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.time(), QLocale::LongFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("OTIME"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIME"; i += strlen("OTIME"); if (d->mOrigMsg) { const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime(); const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat); plainBody.append(str); const QString body = plainToHtml(str); htmlBody.append(body); } } else if (cmd.startsWith(QLatin1String("BLANK"))) { // do nothing qCDebug(TEMPLATEPARSER_LOG) << "Command: BLANK"; i += strlen("BLANK"); } else if (cmd.startsWith(QLatin1String("NOP"))) { // do nothing qCDebug(TEMPLATEPARSER_LOG) << "Command: NOP"; i += strlen("NOP"); } else if (cmd.startsWith(QLatin1String("CLEAR"))) { // clear body buffer; not too useful yet qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEAR"; i += strlen("CLEAR"); plainBody.clear(); htmlBody.clear(); KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos"); header->fromUnicodeString(QString::number(0), "utf-8"); d->mMsg->setHeader(header); } else if (cmd.startsWith(QLatin1String("DEBUGOFF"))) { // turn off debug qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUGOFF"; i += strlen("DEBUGOFF"); d->mDebug = false; } else if (cmd.startsWith(QLatin1String("DEBUG"))) { // turn on debug qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUG"; i += strlen("DEBUG"); d->mDebug = true; } else if (cmd.startsWith(QLatin1String("CURSOR"))) { // turn on debug qCDebug(TEMPLATEPARSER_LOG) << "Command: CURSOR"; int oldI = i; i += strlen("CURSOR"); KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos"); header->fromUnicodeString(QString::number(plainBody.length()), "utf-8"); /* if template is: * FOOBAR * %CURSOR * * Make sure there is an empty line for the cursor otherwise it will be placed at the end of FOOBAR */ if (oldI > 0 && tmpl[ oldI - 1 ] == QLatin1Char('\n') && i == tmpl_len - 1) { plainBody.append(QLatin1Char('\n')); } d->mMsg->setHeader(header); d->mForceCursorPosition = true; //FIXME HTML part for header remaining } else if (cmd.startsWith(QLatin1String("SIGNATURE"))) { qCDebug(TEMPLATEPARSER_LOG) << "Command: SIGNATURE"; i += strlen("SIGNATURE"); plainBody.append(getPlainSignature()); htmlBody.append(getHtmlSignature()); } else { // wrong command, do nothing plainBody.append(c); htmlBody.append(c); } } else if (dnl && (c == QLatin1Char('\n') || c == QLatin1Char('\r'))) { // skip if ((tmpl.size() > i + 1) && ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r')) || (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) { // skip one more i += 1; } dnl = false; } else { plainBody.append(c); if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) { htmlBody.append(QLatin1String("
")); htmlBody.append(c); if (tmpl.size() > i + 1 && ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r')) || (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) { htmlBody.append(tmpl[i + 1]); plainBody.append(tmpl[i + 1]); i += 1; } } else { htmlBody.append(c); } } } // Clear the HTML body if FORCEDPLAIN has set ReplyAsPlain, OR if, // there is no use of FORCED command but a configure setting has ReplyUsingHtml disabled, // OR the original mail has no HTML part. const KMime::Content *content = d->mOrigMsg->mainBodyPart("text/html"); if (d->mQuotes == ReplyAsPlain || (d->mQuotes != ReplyAsHtml && !TemplateParserSettings::self()->replyUsingHtml()) || (!content || !content->hasContent())) { htmlBody.clear(); } else { makeValidHtml(htmlBody); } if (d->mMode == NewMessage && plainBody.isEmpty() && !d->mExtractHtmlInfoResult.mPlainText.isEmpty()) { plainBody = d->mExtractHtmlInfoResult.mPlainText; } /* if (d->mMode == NewMessage && htmlBody.isEmpty() && !d->mExtractHtmlInfoResult.mHtmlElement.isEmpty()) { htmlBody = d->mExtractHtmlInfoResult.mHtmlElement; } */ addProcessedBodyToMessage(plainBody, htmlBody); Q_EMIT parsingDone(d->mForceCursorPosition); deleteLater(); } QString TemplateParserJob::getPlainSignature() const { const KIdentityManagement::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity); if (identity.isNull()) { return QString(); } KIdentityManagement::Signature signature = const_cast(identity).signature(); if (signature.type() == KIdentityManagement::Signature::Inlined && signature.isInlinedHtml()) { return signature.toPlainText(); } else { return signature.rawText(); } } // TODO If %SIGNATURE command is on, then override it with signature from // "KMail configure->General->identity->signature". // There should be no two signatures. QString TemplateParserJob::getHtmlSignature() const { const KIdentityManagement::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity); if (identity.isNull()) { return QString(); } KIdentityManagement::Signature signature = const_cast(identity).signature(); if (!signature.isInlinedHtml()) { signature = signature.rawText().toHtmlEscaped(); return signature.rawText().replace(QLatin1Char('\n'), QStringLiteral("
")); } return signature.rawText(); } void TemplateParserJob::addProcessedBodyToMessage(const QString &plainBody, const QString &htmlBody) const { MessageCore::ImageCollector ic; ic.collectImagesFrom(d->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. const auto parts = d->mMsg->contents(); for (KMime::Content *content : parts) { d->mMsg->removeContent(content, true /*delete*/); } // Set To and CC from the template if (!d->mTo.isEmpty()) { d->mMsg->to()->fromUnicodeString(d->mMsg->to()->asUnicodeString() + QLatin1Char(',') + d->mTo, "utf-8"); } if (!d->mCC.isEmpty()) { d->mMsg->cc()->fromUnicodeString(d->mMsg->cc()->asUnicodeString() + QLatin1Char(',') + d->mCC, "utf-8"); } d->mMsg->contentType()->clear(); // to get rid of old boundary //const QByteArray boundary = KMime::multiPartBoundary(); KMime::Content *const mainTextPart = htmlBody.isEmpty() ? createPlainPartContent(plainBody) : createMultipartAlternativeContent(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; if (d->mMode == Forward) { auto attachments = d->mOrigMsg->attachments(); attachments += d->mOtp->nodeHelper()->attachmentsOfExtraContents(); if (!attachments.isEmpty()) { mainPart = createMultipartMixed(attachments, textPart); mainPart->assemble(); } } d->mMsg->setBody(mainPart->encodedBody()); d->mMsg->setHeader(mainPart->contentType()); d->mMsg->setHeader(mainPart->contentTransferEncoding()); d->mMsg->assemble(); d->mMsg->parse(); } KMime::Content *TemplateParserJob::createMultipartMixed(const QVector &attachments, KMime::Content *textPart) const { KMime::Content *mixedPart = new KMime::Content(d->mMsg.data()); const QByteArray boundary = KMime::multiPartBoundary(); mixedPart->contentType()->setMimeType("multipart/mixed"); mixedPart->contentType()->setBoundary(boundary); mixedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); mixedPart->addContent(textPart); int attachmentNumber = 1; for (KMime::Content *attachment : attachments) { mixedPart->addContent(attachment); // If the content type has no name or filename parameter, add one, since otherwise the name // would be empty in the attachment view of the composer, which looks confusing if (attachment->contentType(false)) { if (!attachment->contentType()->hasParameter(QStringLiteral("name")) && !attachment->contentType()->hasParameter(QStringLiteral("filename"))) { attachment->contentType()->setParameter( QStringLiteral("name"), i18nc("@item:intext", "Attachment %1", attachmentNumber)); } } ++attachmentNumber; } return mixedPart; } KMime::Content *TemplateParserJob::createMultipartRelated(const MessageCore::ImageCollector &ic, KMime::Content *mainTextPart) const { KMime::Content *relatedPart = new KMime::Content(d->mMsg.data()); const QByteArray boundary = KMime::multiPartBoundary(); relatedPart->contentType()->setMimeType("multipart/related"); relatedPart->contentType()->setBoundary(boundary); relatedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); relatedPart->addContent(mainTextPart); for (KMime::Content *image : ic.images()) { qCWarning(TEMPLATEPARSER_LOG) << "Adding" << image->contentID() << "as an embedded image"; relatedPart->addContent(image); } return relatedPart; } KMime::Content *TemplateParserJob::createPlainPartContent(const QString &plainBody) const { KMime::Content *textPart = new KMime::Content(d->mMsg.data()); textPart->contentType()->setMimeType("text/plain"); QTextCodec *charset = selectCharset(d->mCharsets, plainBody); textPart->contentType()->setCharset(charset->name()); textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); textPart->fromUnicodeString(plainBody); return textPart; } KMime::Content *TemplateParserJob::createMultipartAlternativeContent(const QString &plainBody, const QString &htmlBody) const { KMime::Content *multipartAlternative = new KMime::Content(d->mMsg.data()); multipartAlternative->contentType()->setMimeType("multipart/alternative"); const QByteArray boundary = KMime::multiPartBoundary(); multipartAlternative->contentType()->setBoundary(boundary); KMime::Content *textPart = createPlainPartContent(plainBody); multipartAlternative->addContent(textPart); KMime::Content *htmlPart = new KMime::Content(d->mMsg.data()); htmlPart->contentType()->setMimeType("text/html"); QTextCodec *charset = selectCharset(d->mCharsets, htmlBody); htmlPart->contentType()->setCharset(charset->name()); htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit); htmlPart->fromUnicodeString(htmlBody); multipartAlternative->addContent(htmlPart); return multipartAlternative; } QString TemplateParserJob::findCustomTemplate(const QString &tmplName) { CTemplates t(tmplName); d->mTo = t.to(); d->mCC = t.cC(); const QString content = t.content(); if (!content.isEmpty()) { return content; } else { return findTemplate(); } } QString TemplateParserJob::findTemplate() { // qCDebug(TEMPLATEPARSER_LOG) << "Trying to find template for mode" << mode; QString tmpl; qCDebug(TEMPLATEPARSER_LOG) << "Folder identify found:" << d->mFolder; if (d->mFolder >= 0) { // only if a folder was found QString fid = QString::number(d->mFolder); Templates fconf(fid); if (fconf.useCustomTemplates()) { // does folder use custom templates? switch (d->mMode) { case NewMessage: tmpl = fconf.templateNewMessage(); break; case Reply: tmpl = fconf.templateReply(); break; case ReplyAll: tmpl = fconf.templateReplyAll(); break; case Forward: tmpl = fconf.templateForward(); break; default: qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode; return QString(); } d->mQuoteString = fconf.quoteString(); if (!tmpl.isEmpty()) { return tmpl; // use folder-specific template } } } if (!d->mIdentity) { // find identity message belongs to d->mIdentity = identityUoid(d->mMsg); if (!d->mIdentity && d->mOrigMsg) { d->mIdentity = identityUoid(d->mOrigMsg); } d->mIdentity = d->m_identityManager->identityForUoidOrDefault(d->mIdentity).uoid(); if (!d->mIdentity) { qCDebug(TEMPLATEPARSER_LOG) << "Oops! No identity for message"; } } qCDebug(TEMPLATEPARSER_LOG) << "Identity found:" << d->mIdentity; QString iid; if (d->mIdentity) { iid = TemplatesConfiguration::configIdString(d->mIdentity); // templates ID for that identity } else { iid = QStringLiteral("IDENTITY_NO_IDENTITY"); // templates ID for no identity } Templates iconf(iid); if (iconf.useCustomTemplates()) { // does identity use custom templates? switch (d->mMode) { case NewMessage: tmpl = iconf.templateNewMessage(); break; case Reply: tmpl = iconf.templateReply(); break; case ReplyAll: tmpl = iconf.templateReplyAll(); break; case Forward: tmpl = iconf.templateForward(); break; default: qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode; return QString(); } d->mQuoteString = iconf.quoteString(); if (!tmpl.isEmpty()) { return tmpl; // use identity-specific template } } switch (d->mMode) { // use the global template case NewMessage: tmpl = TemplateParserSettings::self()->templateNewMessage(); break; case Reply: tmpl = TemplateParserSettings::self()->templateReply(); break; case ReplyAll: tmpl = TemplateParserSettings::self()->templateReplyAll(); break; case Forward: tmpl = TemplateParserSettings::self()->templateForward(); break; default: qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode; return QString(); } d->mQuoteString = TemplateParserSettings::self()->quoteString(); return tmpl; } QString TemplateParserJob::pipe(const QString &cmd, const QString &buf) { KProcess process; bool success; process.setOutputChannelMode(KProcess::SeparateChannels); process.setShellCommand(cmd); process.start(); if (process.waitForStarted(pipeTimeout())) { bool finished = false; if (!buf.isEmpty()) { process.write(buf.toLatin1()); } if (buf.isEmpty() || process.waitForBytesWritten(pipeTimeout())) { if (!buf.isEmpty()) { process.closeWriteChannel(); } if (process.waitForFinished(pipeTimeout())) { success = (process.exitStatus() == QProcess::NormalExit); finished = true; } else { finished = false; success = false; } } else { success = false; finished = false; } // The process has started, but did not finish in time. Kill it. if (!finished) { process.kill(); } } else { success = false; } if (!success && d->mDebug) { KMessageBox::error( nullptr, xi18nc("@info", "Pipe command %1 failed.", cmd)); } if (success) { return QTextCodec::codecForLocale()->toUnicode(process.readAllStandardOutput()); } else { return QString(); } } void TemplateParserJob::setWordWrap(bool wrap, int wrapColWidth) { d->mWrap = wrap; d->mColWrap = wrapColWidth; } QString TemplateParserJob::plainMessageText(bool aStripSignature, AllowSelection isSelectionAllowed) const { if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) { return d->mSelection; } if (!d->mOrigMsg) { return QString(); } const auto mp = toplevelTextNode(d->mOtp->parsedPart()); QString result = mp->plaintextContent(); if (result.isEmpty()) { result = d->mExtractHtmlInfoResult.mPlainText; } if (aStripSignature) { result = MessageCore::StringUtil::stripSignature(result); } return result; } QString TemplateParserJob::htmlMessageText(bool aStripSignature, AllowSelection isSelectionAllowed) { if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) { //TODO implement d->mSelection for HTML return d->mSelection; } d->mHeadElement = d->mExtractHtmlInfoResult.mHeaderElement; const QString bodyElement = d->mExtractHtmlInfoResult.mBodyElement; if (!bodyElement.isEmpty()) { if (aStripSignature) { //FIXME strip signature works partially for HTML mails return MessageCore::StringUtil::stripSignature(bodyElement); } return bodyElement; } if (aStripSignature) { //FIXME strip signature works partially for HTML mails return MessageCore::StringUtil::stripSignature(d->mExtractHtmlInfoResult.mHtmlElement); } return d->mExtractHtmlInfoResult.mHtmlElement; } QString TemplateParserJob::quotedPlainText(const QString &selection) const { 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, lineStart); } const QString indentStr = MessageCore::StringUtil::formatQuotePrefix(d->mQuoteString, d->mOrigMsg->from()->displayString()); if (TemplateParserSettings::self()->smartQuote() && d->mWrap) { content = MessageCore::StringUtil::smartQuote(content, d->mColWrap - indentStr.length()); } content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr); content.prepend(indentStr); content += QLatin1Char('\n'); return content; } QString TemplateParserJob::quotedHtmlText(const QString &selection) const { 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; } uint TemplateParserJob::identityUoid(const KMime::Message::Ptr &msg) const { QString idString; if (auto hrd = msg->headerByType("X-KMail-Identity")) { idString = hrd->asUnicodeString().trimmed(); } bool ok = false; unsigned int id = idString.toUInt(&ok); if (!ok || id == 0) { id = d->m_identityManager->identityForAddress( msg->to()->asUnicodeString() + QLatin1String(", ") + msg->cc()->asUnicodeString()).uoid(); } return id; } bool TemplateParserJob::isHtmlSignature() const { const KIdentityManagement::Identity &identity = d->m_identityManager->identityForUoid(d->mIdentity); if (identity.isNull()) { return false; } const KIdentityManagement::Signature signature = const_cast(identity).signature(); return signature.isInlinedHtml(); } QString TemplateParserJob::plainToHtml(const QString &body) { QString str = body; str = str.toHtmlEscaped(); str.replace(QLatin1Char('\n'), QStringLiteral("
\n")); return str; } void TemplateParserJob::makeValidHtml(QString &body) { 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("") + d->mHeadElement + QLatin1String("") + body; } body = QLatin1String("") + body + QLatin1String(""); } }