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 }