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 ×tamp) 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 }