File indexing completed on 2024-06-16 05:01:22

0001 /* Copyright (C) 2006 - 2017 Jan Kundrát <jkt@kde.org>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "MessageComposer.h"
0024 #include <QBuffer>
0025 #include <QMimeData>
0026 #include <QMimeDatabase>
0027 #include <QUrl>
0028 #include <QUuid>
0029 #include "Common/Application.h"
0030 #include "Composer/ComposerAttachments.h"
0031 #include "Imap/Encoders.h"
0032 #include "Imap/Model/DragAndDrop.h"
0033 #include "Imap/Model/ItemRoles.h"
0034 #include "Imap/Model/Model.h"
0035 #include "Imap/Model/Utils.h"
0036 #include "UiUtils/IconLoader.h"
0037 
0038 #define CHECK_STREAM_OK_AT_END(STREAM) \
0039     if (!STREAM.atEnd()) { \
0040         qDebug() << "drag-and-drop: cannot decode data: too much data"; \
0041         return false; \
0042     } \
0043     if (STREAM.status() != QDataStream::Ok) { \
0044         qDebug() << "drag-and-drop: cannot decode data: stream error" << STREAM.status(); \
0045         return false; \
0046     }
0047 
0048 namespace Composer {
0049 
0050 MessageComposer::MessageComposer(Imap::Mailbox::Model *model) :
0051     QAbstractListModel(nullptr), m_model(model), m_shouldPreload(false), m_reportTrojitaVersions(true)
0052 {
0053 }
0054 
0055 MessageComposer::~MessageComposer()
0056 {
0057     qDeleteAll(m_attachments);
0058 }
0059 
0060 int MessageComposer::rowCount(const QModelIndex &parent) const
0061 {
0062     return parent.isValid() ? 0 : m_attachments.size();
0063 }
0064 
0065 QVariant MessageComposer::data(const QModelIndex &index, int role) const
0066 {
0067     if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
0068         return QVariant();
0069 
0070     switch (role) {
0071     case Qt::DisplayRole:
0072         return m_attachments[index.row()]->caption();
0073     case Qt::ToolTipRole:
0074         return m_attachments[index.row()]->tooltip();
0075     case Qt::DecorationRole:
0076     {
0077         // This is more or less copy-pasted from Gui/AttachmentView.cpp. Unfortunately, sharing the implementation
0078         // is not trivial due to the way how the static libraries are currently built.
0079         QMimeType mimeType = QMimeDatabase().mimeTypeForName(QString::fromUtf8(m_attachments[index.row()]->mimeType()));
0080         if (mimeType.isValid() && !mimeType.isDefault()) {
0081             return QIcon::fromTheme(mimeType.iconName(), UiUtils::loadIcon(QStringLiteral("mail-attachment")));
0082         } else {
0083             return UiUtils::loadIcon(QStringLiteral("mail-attachment"));
0084         }
0085     }
0086     case Imap::Mailbox::RoleAttachmentContentDispositionMode:
0087         return static_cast<int>(m_attachments[index.row()]->contentDispositionMode());
0088     }
0089     return QVariant();
0090 }
0091 
0092 Qt::DropActions MessageComposer::supportedDropActions() const
0093 {
0094     return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
0095 }
0096 
0097 Qt::ItemFlags MessageComposer::flags(const QModelIndex &index) const
0098 {
0099     Qt::ItemFlags f = QAbstractListModel::flags(index);
0100 
0101     if (index.isValid()) {
0102         f |= Qt::ItemIsDragEnabled;
0103     }
0104     f |= Qt::ItemIsDropEnabled;
0105     return f;
0106 }
0107 
0108 QMimeData *MessageComposer::mimeData(const QModelIndexList &indexes) const
0109 {
0110     QByteArray encodedData;
0111     QDataStream stream(&encodedData, QIODevice::WriteOnly);
0112     stream.setVersion(QDataStream::Qt_4_6);
0113 
0114     QList<AttachmentItem*> items;
0115     Q_FOREACH(const QModelIndex &index, indexes) {
0116         if (index.model() != this || !index.isValid() || index.column() != 0 || index.parent().isValid())
0117             continue;
0118         if (index.row() < 0 || index.row() >= m_attachments.size())
0119             continue;
0120         items << m_attachments[index.row()];
0121     }
0122 
0123     if (items.isEmpty())
0124         return 0;
0125 
0126     stream << items.size();
0127     Q_FOREACH(const AttachmentItem *attachment, items) {
0128         attachment->asDroppableMimeData(stream);
0129     }
0130     QMimeData *res = new QMimeData();
0131     res->setData(Imap::MimeTypes::xTrojitaAttachmentList, encodedData);
0132     return res;
0133 }
0134 
0135 bool MessageComposer::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0136 {
0137     if (action == Qt::IgnoreAction)
0138         return true;
0139 
0140     if (column > 0)
0141         return false;
0142 
0143     if (!m_model)
0144         return false;
0145 
0146     Q_UNUSED(row);
0147     Q_UNUSED(parent);
0148     // FIXME: would be cool to support attachment reshuffling and to respect the desired drop position
0149 
0150 
0151     if (data->hasFormat(Imap::MimeTypes::xTrojitaAttachmentList)) {
0152         QByteArray encodedData = data->data(Imap::MimeTypes::xTrojitaAttachmentList);
0153         QDataStream stream(&encodedData, QIODevice::ReadOnly);
0154         return dropAttachmentList(stream);
0155     } else if (data->hasFormat(Imap::MimeTypes::xTrojitaMessageList)) {
0156         QByteArray encodedData = data->data(Imap::MimeTypes::xTrojitaMessageList);
0157         QDataStream stream(&encodedData, QIODevice::ReadOnly);
0158         return dropImapMessage(stream);
0159     } else if (data->hasFormat(Imap::MimeTypes::xTrojitaImapPart)) {
0160         QByteArray encodedData = data->data(Imap::MimeTypes::xTrojitaImapPart);
0161         QDataStream stream(&encodedData, QIODevice::ReadOnly);
0162         return dropImapPart(stream);
0163     } else if (data->hasUrls()) {
0164         bool attached = false;
0165         QList<QUrl> urls = data->urls();
0166         foreach (const QUrl &url, urls) {
0167             if (url.isLocalFile()) {
0168                 // Careful here -- we definitely don't want the boolean evaluation shortcuts taking effect!
0169                 // At the same time, any file being recognized and attached is enough to "satisfy" the drop
0170                 attached = addFileAttachment(url.path()) || attached;
0171             }
0172         }
0173         return attached;
0174     } else {
0175         return false;
0176     }
0177 }
0178 
0179 /** @short Container wrapper which calls qDeleteAll on all items which remain in the list at the time of destruction */
0180 template <typename T>
0181 class WillDeleteAll {
0182 public:
0183     T d;
0184     ~WillDeleteAll() {
0185         qDeleteAll(d);
0186     }
0187 };
0188 
0189 /** @short Handle a drag-and-drop of a list of attachments */
0190 bool MessageComposer::dropAttachmentList(QDataStream &stream)
0191 {
0192     stream.setVersion(QDataStream::Qt_4_6);
0193     if (stream.atEnd()) {
0194         qDebug() << "drag-and-drop: cannot decode data: end of stream";
0195         return false;
0196     }
0197     int num;
0198     stream >> num;
0199     if (stream.status() != QDataStream::Ok) {
0200         qDebug() << "drag-and-drop: stream failed:" << stream.status();
0201         return false;
0202     }
0203     if (num < 0) {
0204         qDebug() << "drag-and-drop: invalid number of items";
0205         return false;
0206     }
0207 
0208     // A crude RAII here; there are many places where the validation might fail even though we have already allocated memory
0209     WillDeleteAll<QList<AttachmentItem*>> items;
0210 
0211     for (int i = 0; i < num; ++i) {
0212         int kind = -1;
0213         stream >> kind;
0214 
0215         switch (kind) {
0216         case AttachmentItem::ATTACHMENT_IMAP_MESSAGE:
0217         {
0218             QString mailbox;
0219             uint uidValidity;
0220             QList<uint> uids;
0221             stream >> mailbox >> uidValidity >> uids;
0222             if (!validateDropImapMessage(stream, mailbox, uidValidity, uids))
0223                 return false;
0224             if (uids.size() != 1) {
0225                 qDebug() << "drag-and-drop: malformed data for a single message in a mixed list: too many UIDs";
0226                 return false;
0227             }
0228             try {
0229                 items.d << new ImapMessageAttachmentItem(m_model, mailbox, uidValidity, uids.front());
0230             } catch (Imap::UnknownMessageIndex &) {
0231                 return false;
0232             }
0233 
0234             break;
0235         }
0236 
0237         case AttachmentItem::ATTACHMENT_IMAP_PART:
0238         {
0239             QString mailbox;
0240             uint uidValidity;
0241             uint uid;
0242             QByteArray trojitaPath;
0243             if (!validateDropImapPart(stream, mailbox, uidValidity, uid, trojitaPath))
0244                 return false;
0245             try {
0246                 items.d << new ImapPartAttachmentItem(m_model, mailbox, uidValidity, uid, trojitaPath);
0247             } catch (Imap::UnknownMessageIndex &) {
0248                 return false;
0249             }
0250 
0251             break;
0252         }
0253 
0254         case AttachmentItem::ATTACHMENT_FILE:
0255         {
0256             QString fileName;
0257             stream >> fileName;
0258             items.d << new FileAttachmentItem(fileName);
0259             break;
0260         }
0261 
0262         default:
0263             qDebug() << "drag-and-drop: invalid kind of attachment";
0264             return false;
0265         }
0266     }
0267 
0268     CHECK_STREAM_OK_AT_END(stream)
0269 
0270     beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size() + items.d.size() - 1);
0271     Q_FOREACH(AttachmentItem *attachment, items.d) {
0272         if (m_shouldPreload)
0273             attachment->preload();
0274         m_attachments << attachment;
0275     }
0276     items.d.clear();
0277     endInsertRows();
0278 
0279     return true;
0280 }
0281 
0282 /** @short Check that the data representing a list of messages is correct */
0283 bool MessageComposer::validateDropImapMessage(QDataStream &stream, QString &mailbox, uint &uidValidity, QList<uint> &uids) const
0284 {
0285     if (stream.status() != QDataStream::Ok) {
0286         qDebug() << "drag-and-drop: stream failed:" << stream.status();
0287         return false;
0288     }
0289 
0290     Imap::Mailbox::TreeItemMailbox *mboxPtr = m_model->findMailboxByName(mailbox);
0291     if (!mboxPtr) {
0292         qDebug() << "drag-and-drop: mailbox not found";
0293         return false;
0294     }
0295 
0296     if (uids.size() < 1) {
0297         qDebug() << "drag-and-drop: no UIDs passed";
0298         return false;
0299     }
0300     if (!uidValidity) {
0301         qDebug() << "drag-and-drop: invalid UIDVALIDITY";
0302         return false;
0303     }
0304 
0305     return true;
0306 }
0307 
0308 /** @short Handle a drag-and-drop of a list of messages */
0309 bool MessageComposer::dropImapMessage(QDataStream &stream)
0310 {
0311     stream.setVersion(QDataStream::Qt_4_6);
0312     if (stream.atEnd()) {
0313         qDebug() << "drag-and-drop: cannot decode data: end of stream";
0314         return false;
0315     }
0316     QString mailbox;
0317     uint uidValidity;
0318     QList<uint> uids;
0319     stream >> mailbox >> uidValidity >> uids;
0320     if (!validateDropImapMessage(stream, mailbox, uidValidity, uids))
0321         return false;
0322 
0323     CHECK_STREAM_OK_AT_END(stream)
0324 
0325     WillDeleteAll<QList<AttachmentItem*>> items;
0326     Q_FOREACH(const uint uid, uids) {
0327         try {
0328             items.d << new ImapMessageAttachmentItem(m_model, mailbox, uidValidity, uid);
0329         } catch (Imap::UnknownMessageIndex &) {
0330             return false;
0331         }
0332         items.d.last()->setContentDispositionMode(CDN_INLINE);
0333     }
0334     beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size() + uids.size() - 1);
0335     Q_FOREACH(AttachmentItem *attachment, items.d) {
0336         if (m_shouldPreload)
0337             attachment->preload();
0338         m_attachments << attachment;
0339     }
0340     items.d.clear();
0341     endInsertRows();
0342 
0343     return true;
0344 }
0345 
0346 /** @short Check that the data representing a single message part are correct */
0347 bool MessageComposer::validateDropImapPart(QDataStream &stream, QString &mailbox, uint &uidValidity, uint &uid, QByteArray &trojitaPath) const
0348 {
0349     stream >> mailbox >> uidValidity >> uid >> trojitaPath;
0350     if (stream.status() != QDataStream::Ok) {
0351         qDebug() << "drag-and-drop: stream failed:" << stream.status();
0352         return false;
0353     }
0354     Imap::Mailbox::TreeItemMailbox *mboxPtr = m_model->findMailboxByName(mailbox);
0355     if (!mboxPtr) {
0356         qDebug() << "drag-and-drop: mailbox not found";
0357         return false;
0358     }
0359 
0360     if (!uidValidity || !uid || trojitaPath.isEmpty()) {
0361         qDebug() << "drag-and-drop: invalid data";
0362         return false;
0363     }
0364     return true;
0365 }
0366 
0367 /** @short Handle a drag-adn-drop of a list of message parts */
0368 bool MessageComposer::dropImapPart(QDataStream &stream)
0369 {
0370     stream.setVersion(QDataStream::Qt_4_6);
0371     if (stream.atEnd()) {
0372         qDebug() << "drag-and-drop: cannot decode data: end of stream";
0373         return false;
0374     }
0375     QString mailbox;
0376     uint uidValidity;
0377     uint uid;
0378     QByteArray trojitaPath;
0379     if (!validateDropImapPart(stream, mailbox, uidValidity, uid, trojitaPath))
0380         return false;
0381 
0382     CHECK_STREAM_OK_AT_END(stream)
0383 
0384     AttachmentItem *item;
0385     try {
0386         item = new ImapPartAttachmentItem(m_model, mailbox, uidValidity, uid, trojitaPath);
0387     } catch (Imap::UnknownMessageIndex &) {
0388         return false;
0389     }
0390 
0391     beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size());
0392     m_attachments << item;
0393     if (m_shouldPreload)
0394         m_attachments.back()->preload();
0395     endInsertRows();
0396 
0397     return true;
0398 }
0399 
0400 QStringList MessageComposer::mimeTypes() const
0401 {
0402     return QStringList() << Imap::MimeTypes::xTrojitaMessageList << Imap::MimeTypes::xTrojitaImapPart << Imap::MimeTypes::xTrojitaAttachmentList << QStringLiteral("text/uri-list");
0403 }
0404 
0405 void MessageComposer::setFrom(const Imap::Message::MailAddress &from)
0406 {
0407     m_from = from;
0408 }
0409 
0410 void MessageComposer::setRecipients(const QList<QPair<Composer::RecipientKind, Imap::Message::MailAddress> > &recipients)
0411 {
0412     m_recipients = recipients;
0413 }
0414 
0415 /** @short Set the value for the In-Reply-To header as per RFC 5322, section 3.6.4
0416 
0417 The expected values to be passed here do *not* contain the angle brackets. This is in accordance with
0418 the very last sentence of that section which says that the angle brackets are not part of the msg-id.
0419 */
0420 void MessageComposer::setInReplyTo(const QList<QByteArray> &inReplyTo)
0421 {
0422     m_inReplyTo = inReplyTo;
0423 }
0424 
0425 /** @short Set the value for the References header as per RFC 5322, section 3.6.4
0426 
0427 @see setInReplyTo
0428 */
0429 void MessageComposer::setReferences(const QList<QByteArray> &references)
0430 {
0431     m_references = references;
0432 }
0433 
0434 void MessageComposer::setTimestamp(const QDateTime &timestamp)
0435 {
0436     m_timestamp = timestamp;
0437 }
0438 
0439 void MessageComposer::setSubject(const QString &subject)
0440 {
0441     m_subject = subject;
0442 }
0443 
0444 void MessageComposer::setOrganization(const QString &organization)
0445 {
0446     m_organization = organization;
0447 }
0448 
0449 void MessageComposer::setText(const QString &text)
0450 {
0451     m_text = text;
0452 }
0453 
0454 bool MessageComposer::isReadyForSerialization() const
0455 {
0456     Q_FOREACH(const AttachmentItem *attachment, m_attachments) {
0457         if (!attachment->isAvailableLocally())
0458             return false;
0459     }
0460     return true;
0461 }
0462 
0463 void MessageComposer::ensureRandomStrings() const
0464 {
0465     if (m_messageId.isNull()) {
0466         m_messageId = AbstractComposer::generateMessageId(m_from);
0467     }
0468 
0469     if (m_mimeBoundary.isNull()) {
0470         // Usage of "=_" is recommended by RFC2045 as it's guaranteed to never occur in a quoted-printable source.
0471 
0472         // We don't bother with checking that our boundary is not present in the individual parts. That's arguably wrong,
0473         // but we don't have much choice if we ever plan to use CATENATE.  It also looks like this is exactly how other MUAs
0474         // operate as well, so let's just join the universal dontcareism here.
0475         m_mimeBoundary = QByteArray("trojita=_") + QUuid::createUuid().toByteArray().replace("{", "").replace("}", "");
0476     }
0477 }
0478 
0479 QByteArray MessageComposer::encodeHeaderField(const QString &text)
0480 {
0481     /* This encodes an "unstructured" header field */
0482     return Imap::encodeRFC2047StringWithAsciiPrefix(text);
0483 }
0484 
0485 namespace {
0486 
0487 /** @short Write a list of recipients into an output buffer */
0488 static void processListOfRecipientsIntoHeader(const QByteArray &prefix, const QList<QByteArray> &addresses, QByteArray &out)
0489 {
0490     // Qt and STL are different, it looks like we cannot easily use something as simple as the ostream_iterator here :(
0491     if (!addresses.isEmpty()) {
0492         out.append(prefix);
0493         for (int i = 0; i < addresses.size() - 1; ++i)
0494             out.append(addresses[i]).append(",\r\n ");
0495         out.append(addresses.last()).append("\r\n");
0496     }
0497 }
0498 
0499 }
0500 
0501 void MessageComposer::writeCommonMessageBeginning(QIODevice *target) const
0502 {
0503     // The From header
0504     target->write(QByteArray("From: ").append(m_from.asMailHeader()).append("\r\n"));
0505 
0506     // All recipients
0507     // Got to group the headers so that both of (To, Cc) are present at most once
0508     QList<QByteArray> rcptTo, rcptCc;
0509     for (auto it = m_recipients.begin(); it != m_recipients.end(); ++it) {
0510         switch(it->first) {
0511         case Composer::ADDRESS_TO:
0512             rcptTo << it->second.asMailHeader();
0513             break;
0514         case Composer::ADDRESS_CC:
0515             rcptCc << it->second.asMailHeader();
0516             break;
0517         case Composer::ADDRESS_BCC:
0518             break;
0519         case Composer::ADDRESS_FROM:
0520         case Composer::ADDRESS_SENDER:
0521         case Composer::ADDRESS_REPLY_TO:
0522         case Composer::ADDRESS_RESENT_FROM:
0523         case Composer::ADDRESS_RESENT_SENDER:
0524         case Composer::ADDRESS_RESENT_TO:
0525         case Composer::ADDRESS_RESENT_CC:
0526         case Composer::ADDRESS_RESENT_BCC:
0527             // These should never ever be produced by Trojita for now
0528             Q_ASSERT(false);
0529             break;
0530         }
0531     }
0532 
0533     QByteArray recipientHeaders;
0534     processListOfRecipientsIntoHeader("To: ", rcptTo, recipientHeaders);
0535     processListOfRecipientsIntoHeader("Cc: ", rcptCc, recipientHeaders);
0536     target->write(recipientHeaders);
0537 
0538     // Other message metadata
0539     target->write(encodeHeaderField(QLatin1String("Subject: ") + m_subject) + "\r\n" +
0540                   "Date: " + Imap::dateTimeToRfc2822(m_timestamp).toUtf8() + "\r\n" +
0541                   "MIME-Version: 1.0\r\n");
0542     target->write("Message-ID: <" + m_messageId + ">\r\n");
0543     writeHeaderWithMsgIds(target, "In-Reply-To", m_inReplyTo);
0544     writeHeaderWithMsgIds(target, "References", m_references);
0545     if (!m_organization.isEmpty()) {
0546         target->write(encodeHeaderField(QLatin1String("Organization: ") + m_organization) + "\r\n");
0547     }
0548     if (m_reportTrojitaVersions) {
0549         target->write(QStringLiteral("User-Agent: Trojita/%1; %2\r\n").arg(
0550                       Common::Application::version, Imap::Mailbox::systemPlatformVersion()).toUtf8());
0551     } else {
0552         target->write("User-Agent: Trojita\r\n");
0553     }
0554 
0555     // Headers depending on actual message body data
0556     if (!m_attachments.isEmpty()) {
0557         target->write("Content-Type: multipart/mixed;\r\n\tboundary=\"" + m_mimeBoundary + "\"\r\n"
0558                       "\r\nThis is a multipart/mixed message in MIME format.\r\n\r\n"
0559                       "--" + m_mimeBoundary + "\r\n");
0560     }
0561 
0562     target->write("Content-Type: text/plain; charset=utf-8; format=flowed\r\n"
0563                   "Content-Transfer-Encoding: quoted-printable\r\n"
0564                   "\r\n");
0565     target->write(Imap::quotedPrintableEncode(Imap::wrapFormatFlowed(m_text).toUtf8()));
0566 }
0567 
0568 /** @short Write a header consisting of a list of message-ids
0569 
0570 Empty headers will not be produced, and the result is wrapped at an appropriate length.
0571 
0572 The header name must not contain the colon, it is added automatically.
0573 */
0574 void MessageComposer::writeHeaderWithMsgIds(QIODevice *target, const QByteArray &headerName,
0575                                             const QList<QByteArray> &messageIds) const
0576 {
0577     if (messageIds.isEmpty())
0578         return;
0579 
0580     target->write(headerName + ":");
0581     int charCount = headerName.length() + 1;
0582     for (int i = 0; i < messageIds.size(); ++i) {
0583         // Wrapping shall happen at 78 columns, three bytes are eaten by "space < >"
0584         if (i != 0 && charCount != 0 && charCount + messageIds[i].length() > 78 - 3) {
0585             // got to wrap the header to respect a reasonably small line size
0586             charCount = 0;
0587             target->write("\r\n");
0588         }
0589         // and now just append one more item
0590         target->write(" <" + messageIds[i] + ">");
0591         charCount += messageIds[i].length() + 3;
0592     }
0593     target->write("\r\n");
0594 }
0595 
0596 bool MessageComposer::writeAttachmentHeader(QIODevice *target, QString *errorMessage, const AttachmentItem *attachment) const
0597 {
0598     if (!attachment->isAvailableLocally() && attachment->imapUrl().isEmpty()) {
0599         *errorMessage = tr("Attachment %1 is not available").arg(attachment->caption());
0600         return false;
0601     }
0602     target->write("\r\n--" + m_mimeBoundary + "\r\n"
0603                   "Content-Type: " + attachment->mimeType() + "\r\n");
0604     target->write(attachment->contentDispositionHeader());
0605 
0606     switch (attachment->suggestedCTE()) {
0607     case AttachmentItem::ContentTransferEncoding::Base64:
0608         target->write("Content-Transfer-Encoding: base64\r\n");
0609         break;
0610     case AttachmentItem::ContentTransferEncoding::SevenBit:
0611         target->write("Content-Transfer-Encoding: 7bit\r\n");
0612         break;
0613     case AttachmentItem::ContentTransferEncoding::EightBit:
0614         target->write("Content-Transfer-Encoding: 8bit\r\n");
0615         break;
0616     case AttachmentItem::ContentTransferEncoding::Binary:
0617         target->write("Content-Transfer-Encoding: binary\r\n");
0618         break;
0619     case AttachmentItem::ContentTransferEncoding::QuotedPrintable:
0620         target->write("Content-Transfer-Encoding: quoted-printable\r\n");
0621     }
0622 
0623     target->write("\r\n");
0624     return true;
0625 }
0626 
0627 bool MessageComposer::writeAttachmentBody(QIODevice *target, QString *errorMessage, const AttachmentItem *attachment) const
0628 {
0629     if (!attachment->isAvailableLocally()) {
0630         *errorMessage = tr("Attachment %1 is not available").arg(attachment->caption());
0631         return false;
0632     }
0633     QSharedPointer<QIODevice> io = attachment->rawData();
0634     if (!io) {
0635         *errorMessage = tr("Attachment %1 disappeared").arg(attachment->caption());
0636         return false;
0637     }
0638     while (!io->atEnd()) {
0639         switch (attachment->suggestedCTE()) {
0640         case AttachmentItem::ContentTransferEncoding::Base64:
0641             // Base64 maps 6bit chunks into a single byte. Output shall have no more than 76 characters per line
0642             // (not counting the CRLF pair).
0643             target->write(io->read(76*6/8).toBase64() + "\r\n");
0644             break;
0645         case AttachmentItem::ContentTransferEncoding::QuotedPrintable:
0646             target->write(Imap::quotedPrintableEncode(io->readAll()));
0647             break;
0648         case AttachmentItem::ContentTransferEncoding::SevenBit:
0649         case AttachmentItem::ContentTransferEncoding::EightBit:
0650         case AttachmentItem::ContentTransferEncoding::Binary:
0651             target->write(io->readAll());
0652             break;
0653         }
0654     }
0655     return true;
0656 }
0657 
0658 bool MessageComposer::asRawMessage(QIODevice *target, QString *errorMessage) const
0659 {
0660     ensureRandomStrings();
0661 
0662     writeCommonMessageBeginning(target);
0663 
0664     if (!m_attachments.isEmpty()) {
0665         Q_FOREACH(const AttachmentItem *attachment, m_attachments) {
0666             if (!writeAttachmentHeader(target, errorMessage, attachment))
0667                 return false;
0668             if (!writeAttachmentBody(target, errorMessage, attachment))
0669                 return false;
0670         }
0671         target->write("\r\n--" + m_mimeBoundary + "--\r\n");
0672     }
0673     return true;
0674 }
0675 
0676 bool MessageComposer::asCatenateData(QList<Imap::Mailbox::CatenatePair> &target, QString *errorMessage) const
0677 {
0678     ensureRandomStrings();
0679 
0680     using namespace Imap::Mailbox;
0681     target.clear();
0682     target.append(qMakePair(CATENATE_TEXT, QByteArray()));
0683 
0684     // write the initial data
0685     {
0686         QBuffer io(&target.back().second);
0687         io.open(QIODevice::ReadWrite);
0688         writeCommonMessageBeginning(&io);
0689     }
0690 
0691     if (!m_attachments.isEmpty()) {
0692         Q_FOREACH(const AttachmentItem *attachment, m_attachments) {
0693             if (target.back().first != CATENATE_TEXT) {
0694                 target.append(qMakePair(CATENATE_TEXT, QByteArray()));
0695             }
0696             QBuffer io(&target.back().second);
0697             io.open(QIODevice::Append);
0698 
0699             if (!writeAttachmentHeader(&io, errorMessage, attachment))
0700                 return false;
0701 
0702             QByteArray url = attachment->imapUrl();
0703             if (url.isEmpty()) {
0704                 // Cannot use CATENATE here
0705                 if (!writeAttachmentBody(&io, errorMessage, attachment))
0706                     return false;
0707             } else {
0708                 target.append(qMakePair(CATENATE_URL, url));
0709             }
0710         }
0711         if (target.back().first != CATENATE_TEXT) {
0712             target.append(qMakePair(CATENATE_TEXT, QByteArray()));
0713         }
0714         QBuffer io(&target.back().second);
0715         io.open(QIODevice::Append);
0716         io.write("\r\n--" + m_mimeBoundary + "--\r\n");
0717     }
0718     return true;
0719 }
0720 
0721 QDateTime MessageComposer::timestamp() const
0722 {
0723     return m_timestamp;
0724 }
0725 
0726 QList<QByteArray> MessageComposer::inReplyTo() const
0727 {
0728     return m_inReplyTo;
0729 }
0730 
0731 QList<QByteArray> MessageComposer::references() const
0732 {
0733     return m_references;
0734 }
0735 
0736 QByteArray MessageComposer::rawFromAddress() const
0737 {
0738     return m_from.asSMTPMailbox();
0739 }
0740 
0741 QList<QByteArray> MessageComposer::rawRecipientAddresses() const
0742 {
0743     QList<QByteArray> res;
0744 
0745     for (auto it = m_recipients.begin(); it != m_recipients.end(); ++it) {
0746         res << it->second.asSMTPMailbox();
0747     }
0748 
0749     return res;
0750 }
0751 
0752 bool MessageComposer::addFileAttachment(const QString &path)
0753 {
0754     beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size());
0755     QScopedPointer<AttachmentItem> attachment(new FileAttachmentItem(path));
0756     if (!attachment->isAvailableLocally())
0757         return false;
0758     if (m_shouldPreload)
0759         attachment->preload();
0760     m_attachments << attachment.take();
0761     endInsertRows();
0762     return true;
0763 }
0764 
0765 void MessageComposer::removeAttachment(const QModelIndex &index)
0766 {
0767     if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
0768         return;
0769 
0770     beginRemoveRows(QModelIndex(), index.row(), index.row());
0771     delete m_attachments.takeAt(index.row());
0772     endRemoveRows();
0773 }
0774 
0775 void MessageComposer::setAttachmentName(const QModelIndex &index, const QString &newName)
0776 {
0777     if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
0778         return;
0779 
0780     if (m_attachments[index.row()]->setPreferredFileName(newName))
0781         emit dataChanged(index, index);
0782 }
0783 
0784 void MessageComposer::setAttachmentContentDisposition(const QModelIndex &index, const ContentDisposition disposition)
0785 {
0786     if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_attachments.size())
0787         return;
0788 
0789     if (m_attachments[index.row()]->setContentDispositionMode(disposition))
0790         emit dataChanged(index, index);
0791 }
0792 
0793 void MessageComposer::setPreloadEnabled(const bool preload)
0794 {
0795     m_shouldPreload = preload;
0796 }
0797 
0798 void MessageComposer::setReplyingToMessage(const QModelIndex &index)
0799 {
0800     m_replyingTo = index;
0801 }
0802 
0803 QModelIndex MessageComposer::replyingToMessage() const
0804 {
0805     return m_replyingTo;
0806 }
0807 
0808 QModelIndex MessageComposer::forwardingMessage() const
0809 {
0810     return m_forwarding;
0811 }
0812 
0813 void MessageComposer::prepareForwarding(const QModelIndex &index, const ForwardMode mode)
0814 {
0815     m_forwarding = index;
0816 
0817     switch (mode) {
0818     case Composer::ForwardMode::FORWARD_AS_ATTACHMENT:
0819     {
0820         beginInsertRows(QModelIndex(), m_attachments.size(), m_attachments.size());
0821         QString mailbox = m_forwarding.data(Imap::Mailbox::RoleMailboxName).toString();
0822         uint uidValidity = m_forwarding.data(Imap::Mailbox::RoleMailboxUidValidity).toUInt();
0823         uint uid = m_forwarding.data(Imap::Mailbox::RoleMessageUid).toUInt();
0824         QScopedPointer<AttachmentItem> attachment(new ImapMessageAttachmentItem(m_model, mailbox, uidValidity, uid));
0825         if (m_shouldPreload) {
0826             attachment->preload();
0827         }
0828         attachment->setContentDispositionMode(CDN_INLINE);
0829         m_attachments << attachment.take();
0830         endInsertRows();
0831         break;
0832     }
0833     }
0834 }
0835 
0836 void MessageComposer::setReportTrojitaVersions(const bool reportVersions)
0837 {
0838     m_reportTrojitaVersions = reportVersions;
0839 }
0840 
0841 }