File indexing completed on 2024-05-26 05:28:09

0001 /* Copyright (C) 2014-2015 Stephan Platz <trojita@paalsteek.de>
0002    Copyright (C) 2006 - 2016 Jan Kundrát <jkt@kde.org>
0003 
0004    This file is part of the Trojita Qt IMAP e-mail client,
0005    http://trojita.flaska.net/
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU General Public License as
0009    published by the Free Software Foundation; either version 2 of
0010    the License or (at your option) version 3 or any later version
0011    accepted by the membership of KDE e.V. (or its successor approved
0012    by the membership of KDE e.V.), which shall act as a proxy
0013    defined in Section 14 of version 3 of the license.
0014 
0015    This program is distributed in the hope that it will be useful,
0016    but WITHOUT ANY WARRANTY; without even the implied warranty of
0017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0018    GNU General Public License for more details.
0019 
0020    You should have received a copy of the GNU General Public License
0021    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0022 */
0023 
0024 #include <cstdint> // workaround broken mimetic header
0025 #include <sstream>
0026 #include <mimetic/mimetic.h>
0027 #include "Cryptography/MimeticUtils.h"
0028 #include "Imap/Encoders.h"
0029 #include "Imap/Model/ItemRoles.h"
0030 #include "Imap/Parser/Message.h"
0031 #include "Imap/Parser/Rfc5322HeaderParser.h"
0032 
0033 namespace Cryptography {
0034 
0035 void MimeticUtils::storeInterestingFields(const mimetic::MimeEntity &me, LocalMessagePart *part)
0036 {
0037     part->setCharset(me.header().contentType().param("charset").c_str());
0038     QByteArray format = me.header().contentType().param("format").c_str();
0039     if (!format.isEmpty()) {
0040         part->setContentFormat(format.toLower());
0041         part->setDelSp(me.header().contentType().param("delsp").c_str());
0042     }
0043     Imap::Message::AbstractMessage::bodyFldParam_t bodyFldParam;
0044     for (const auto &item: me.header().contentType().paramList()) {
0045         bodyFldParam[QByteArray(item.name().c_str()).toUpper()] = QByteArray(item.value().c_str());
0046     }
0047     part->setBodyFldParam(bodyFldParam);
0048     const mimetic::ContentDisposition &cd = me.header().contentDisposition();
0049     QByteArray bodyDisposition = cd.type().c_str();
0050     QString filename;
0051     if (!bodyDisposition.isEmpty()) {
0052         part->setBodyDisposition(bodyDisposition);
0053         const std::list<mimetic::ContentDisposition::Param> l = cd.paramList();
0054         QMap<QByteArray, QByteArray> paramMap;
0055         for (auto it = l.begin(); it != l.end(); ++it) {
0056             const mimetic::ContentDisposition::Param &p = *it;
0057             paramMap.insert(p.name().c_str(), p.value().c_str());
0058         }
0059         filename = Imap::extractRfc2231Param(paramMap, "filename");
0060     }
0061     if (filename.isEmpty()) {
0062         const std::list<mimetic::ContentType::Param> l = me.header().contentType().paramList();
0063         QMap<QByteArray, QByteArray> paramMap;
0064         for (auto it = l.begin(); it != l.end(); ++it) {
0065             const mimetic::ContentType::Param &p = *it;
0066             paramMap.insert(p.name().c_str(), p.value().c_str());
0067         }
0068         filename = Imap::extractRfc2231Param(paramMap, "name");
0069     }
0070 
0071     if (!filename.isEmpty()) {
0072         part->setFilename(filename);
0073     }
0074 
0075     // The following header fields do not make sense for multiparts
0076     if (!me.header().contentType().isMultipart()) {
0077         part->setTransferEncoding(me.header().contentTransferEncoding().str().c_str());
0078         part->setBodyFldId(me.header().contentId().str().c_str());
0079     }
0080 
0081     // The number of octets doesn't have an equivalent in headers, so we aren't storing it
0082 }
0083 
0084 QList<Imap::Message::MailAddress> MimeticUtils::mailboxListToQList(const mimetic::MailboxList &list)
0085 {
0086     QList<Imap::Message::MailAddress> result;
0087     for (const mimetic::Mailbox &addr: list)
0088     {
0089         result.append(Imap::Message::MailAddress(
0090                           Imap::decodeRFC2047String(addr.label().data()),
0091                           QString::fromStdString(addr.sourceroute()),
0092                           QString::fromStdString(addr.mailbox()),
0093                           QString::fromStdString(addr.domain())));
0094     }
0095     return result;
0096 }
0097 
0098 QList<Imap::Message::MailAddress> MimeticUtils::addressListToQList(const mimetic::AddressList &list)
0099 {
0100     QList<Imap::Message::MailAddress> result;
0101     for (const mimetic::Address &addr: list)
0102     {
0103         if (addr.isGroup()) {
0104             const mimetic::Group group = addr.group();
0105             for (mimetic::Group::const_iterator it = group.begin(), end = group.end(); it != end; it++) {
0106                 const mimetic::Mailbox mb = *it;
0107                 result.append(Imap::Message::MailAddress(
0108                                   Imap::decodeRFC2047String(mb.label().data()),
0109                                   QString::fromStdString(mb.sourceroute()),
0110                                   QString::fromStdString(mb.mailbox()),
0111                                   QString::fromStdString(mb.domain())));
0112             }
0113         } else {
0114             const mimetic::Mailbox mb = addr.mailbox();
0115             result.append(Imap::Message::MailAddress(
0116                               Imap::decodeRFC2047String(mb.label().data()),
0117                               QString::fromStdString(mb.sourceroute()),
0118                               QString::fromStdString(mb.mailbox()),
0119                               QString::fromStdString(mb.domain())));
0120         }
0121     }
0122     return result;
0123 }
0124 
0125 QByteArray dumpMimeHeader(const mimetic::Header &h)
0126 {
0127     std::stringstream ss;
0128     for (auto it = h.begin(), end = h.end(); it != end; ++it) {
0129         it->write(ss) << "\r\n";
0130     }
0131     return ss.str().c_str();
0132 }
0133 
0134 MessagePart::Ptr MimeticUtils::mimeEntityToPart(const mimetic::MimeEntity &me, MessagePart *parent, const int row)
0135 {
0136     QByteArray type, subtype;
0137     if (std::find_if(me.header().cbegin(), me.header().cend(), [](const mimetic::Field &f) {
0138                      return QByteArray(f.name().c_str()).toLower() == QByteArrayLiteral("content-type");
0139                     }) != me.header().cend()) {
0140         // When I checked Mimetic's  me.header().hasField(), it looked like it is case-sensitive...
0141         type = QByteArray(me.header().contentType().type().c_str()).toLower();
0142         subtype = QByteArray(me.header().contentType().subtype().c_str()).toLower();
0143     } else {
0144         // Section 5.2 of RFC 2045 says that there's an implicit text/plain; charset=us-ascii in case this header is missing,
0145         // see https://tools.ietf.org/html/rfc2045#section-5.2 .
0146         // We don't implement the us-ascii encoding because our encoders are quite happy to guess anyway, and utf-8
0147         // is a subset of us-ascii anyway.
0148         type = QByteArrayLiteral("text");
0149         subtype = QByteArrayLiteral("plain");
0150     }
0151     std::unique_ptr<LocalMessagePart> part(new LocalMessagePart(parent, row, type + "/" + subtype));
0152     storeInterestingFields(me, part.get());
0153     std::unique_ptr<LocalMessagePart> headerPart, textPart, mimePart, rawPart;
0154     bool isMessageRfc822 = false;
0155     if (me.body().parts().size() > 0) {
0156         if (type == QByteArray("message") && subtype == QByteArray("rfc822")) {
0157             isMessageRfc822 = true;
0158             const mimetic::Header h = (*(me.body().parts().begin()))->header();
0159             QDateTime date = QDateTime::fromString(QString::fromStdString(h.field("date").value()));
0160             QString subject = Imap::decodeRFC2047String(h.subject().data());
0161             QList<Imap::Message::MailAddress> from, sender, replyTo, to, cc, bcc;
0162             from = mailboxListToQList(h.from());
0163             if (h.hasField("sender")) {
0164                 sender.append(Imap::Message::MailAddress(
0165                                   Imap::decodeRFC2047String(h.sender().label().data()),
0166                                   QString::fromStdString(h.sender().sourceroute()),
0167                                   QString::fromStdString(h.sender().mailbox()),
0168                                   QString::fromStdString(h.sender().domain())));
0169             }
0170             replyTo = addressListToQList(h.replyto());
0171             to = addressListToQList(h.to());
0172             cc = addressListToQList(h.cc());
0173             bcc = addressListToQList(h.bcc());
0174             Imap::LowLevelParser::Rfc5322HeaderParser headerParser;
0175             QByteArray rawHeader = dumpMimeHeader(h);
0176             bool ok = headerParser.parse(rawHeader);
0177             if (!ok) {
0178                 qDebug() << QLatin1String("Unspecified error during RFC5322 header parsing");
0179             } else {
0180                 part->setHdrReferences(headerParser.references);
0181                 if (!headerParser.listPost.isEmpty()) {
0182                     QList<QUrl> listPost;
0183                     Q_FOREACH(const QByteArray &item, headerParser.listPost)
0184                         listPost << QUrl(QString::fromUtf8(item));
0185                     part->setHdrListPost(listPost);
0186                 }
0187                 if (headerParser.listPostNo)
0188                     part->setHdrListPostNo(true);
0189             }
0190             QByteArray messageId = headerParser.messageId.size() == 1 ? headerParser.messageId.front() : QByteArray();
0191             part->setEnvelope(std::unique_ptr<Imap::Message::Envelope>(
0192                                   new Imap::Message::Envelope(date, subject, from, sender, replyTo, to, cc, bcc, headerParser.inReplyTo, messageId)));
0193         }
0194         int i = 0;
0195         for (const mimetic::MimeEntity* child: me.body().parts()) {
0196             part->setChild(i, mimeEntityToPart(*child, part.get(), i));
0197             ++i;
0198         }
0199         // Remember full part data for part download
0200         std::stringstream ss;
0201         ss << me;
0202         QByteArray rawPartData = ss.str().c_str();
0203         rawPart.reset(new LocalMessagePart(part.get(), 0, QByteArray()));
0204         rawPart->setData(rawPartData);
0205 
0206         // yeah, the raw data of these MIME containers have to be made available, too
0207         part->setData(rawPartData);
0208 
0209         // It seems that Mimetic doesn't really offer us access to the raw content of the MIME tree (the unaltered input).
0210         // Instead, the operator<< simply re-creates some MIME structure based on the parsing results.
0211         // This has a side effect that some body-fld-param are enriched a bit with stuff like quoting, etc.
0212         if (isMessageRfc822) {
0213             const QByteArray headerBoundary = QByteArrayLiteral("\r\n\r\n");
0214             // OK, the first index denotes the position of the end of our artificial header (that message/rfc822 thingy)
0215             auto headerStart = rawPartData.indexOf(headerBoundary);
0216             // ...so let's find a real boundary between the message's header and text fields
0217             if (headerStart != -1) {
0218                 auto bodyStart = rawPartData.indexOf(headerBoundary, headerStart + 1);
0219                 if (bodyStart != -1) {
0220                     headerPart.reset(new LocalMessagePart(part.get(), 0, QByteArray()));
0221                     headerPart->setData(rawPartData.mid(headerStart + headerBoundary.size(), bodyStart - headerStart));
0222                     textPart.reset(new LocalMessagePart(part.get(), 0, QByteArray()));
0223                     textPart->setData(rawPartData.mid(bodyStart + headerBoundary.size()));
0224                 }
0225             }
0226         }
0227     } else {
0228         auto rawPartData = QByteArray(me.body().data(), me.body().size());
0229         rawPart.reset(new LocalMessagePart(part.get(), 0, QByteArray()));
0230         rawPart->setData(rawPartData);
0231         QByteArray data;
0232         Imap::decodeContentTransferEncoding(rawPartData, part->data(Imap::Mailbox::RolePartTransferEncoding).toByteArray().toLower(), &data);
0233         part->setData(data);
0234     }
0235     part->setOctets(me.size());
0236     part->setSpecialParts(std::move(headerPart), std::move(textPart), std::move(mimePart), std::move(rawPart));
0237     return MessagePart::Ptr(std::move(part));
0238 }
0239 
0240 }