File indexing completed on 2024-09-22 04:52:49

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
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 <typeinfo>
0024 
0025 #include <QTextDocument>
0026 #include <QUrl>
0027 #include <QTextCodec>
0028 #include "Message.h"
0029 #include "MailAddress.h"
0030 #include "LowLevelParser.h"
0031 #include "../Model/MailboxTree.h"
0032 #include "../Encoders.h"
0033 #include "../Parser/Rfc5322HeaderParser.h"
0034 
0035 namespace Imap
0036 {
0037 namespace Message
0038 {
0039 
0040 QList<MailAddress> Envelope::getListOfAddresses(const QVariant &in, const QByteArray &line, const int start)
0041 {
0042     if (in.type() == QVariant::ByteArray) {
0043         if (! in.toByteArray().isNull())
0044             throw UnexpectedHere("getListOfAddresses: byte array not null", line, start);
0045     } else if (in.type() != QVariant::List) {
0046         throw ParseError("getListOfAddresses: not a list", line, start);
0047     }
0048 
0049     QVariantList list = in.toList();
0050     QList<MailAddress> res;
0051     for (QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) {
0052         if (it->type() != QVariant::List)
0053             throw UnexpectedHere("getListOfAddresses: split item not a list", line, start);   // FIXME: wrong offset
0054         res.append(MailAddress(it->toList(), line, start));
0055     }
0056     return res;
0057 }
0058 
0059 Envelope Envelope::fromList(const QVariantList &items, const QByteArray &line, const int start)
0060 {
0061     if (items.size() != 10)
0062         throw ParseError("Envelope::fromList: size != 10", line, start);   // FIXME: wrong offset
0063 
0064     // date
0065     QDateTime date;
0066     if (items[0].type() == QVariant::ByteArray) {
0067         QByteArray dateStr = items[0].toByteArray();
0068         if (! dateStr.isEmpty()) {
0069             try {
0070                 date = LowLevelParser::parseRFC2822DateTime(dateStr);
0071             } catch (ParseError &) {
0072                 // FIXME: log this
0073                 //throw ParseError( e.what(), line, start );
0074             }
0075         }
0076     }
0077     // Otherwise it's "invalid", null.
0078 
0079     QString subject = Imap::decodeRFC2047String(items[1].toByteArray());
0080 
0081     QList<MailAddress> from, sender, replyTo, to, cc, bcc;
0082     from = Envelope::getListOfAddresses(items[2], line, start);
0083     sender = Envelope::getListOfAddresses(items[3], line, start);
0084     replyTo = Envelope::getListOfAddresses(items[4], line, start);
0085     to = Envelope::getListOfAddresses(items[5], line, start);
0086     cc = Envelope::getListOfAddresses(items[6], line, start);
0087     bcc = Envelope::getListOfAddresses(items[7], line, start);
0088 
0089     LowLevelParser::Rfc5322HeaderParser headerParser;
0090 
0091     if (items[8].type() != QVariant::ByteArray)
0092         throw UnexpectedHere("Envelope::fromList: inReplyTo not a QByteArray", line, start);
0093     QByteArray inReplyTo = items[8].toByteArray();
0094 
0095     if (items[9].type() != QVariant::ByteArray)
0096         throw UnexpectedHere("Envelope::fromList: messageId not a QByteArray", line, start);
0097     QByteArray messageId = items[9].toByteArray();
0098 
0099     QByteArray buf;
0100     if (!messageId.isEmpty())
0101         buf += "Message-Id: " + messageId + "\r\n";
0102     if (!inReplyTo.isEmpty())
0103         buf += "In-Reply-To: " + inReplyTo + "\r\n";
0104     if (!buf.isEmpty()) {
0105         bool ok = headerParser.parse(buf);
0106         if (!ok) {
0107             qDebug() << "Envelope::fromList: malformed headers";
0108         }
0109     }
0110     // If the Message-Id fails to parse, well, bad luck. This enforced sanitizaion is hopefully better than
0111     // generating garbage in outgoing e-mails.
0112     messageId = headerParser.messageId.size() == 1 ? headerParser.messageId.front() : QByteArray();
0113 
0114     return Envelope(date, subject, from, sender, replyTo, to, cc, bcc, headerParser.inReplyTo, messageId);
0115 }
0116 
0117 void Envelope::clear()
0118 {
0119     date = QDateTime();
0120     subject.clear();
0121     from.clear();
0122     sender.clear();
0123     replyTo.clear();
0124     to.clear();
0125     cc.clear();
0126     bcc.clear();
0127     inReplyTo.clear();
0128     messageId.clear();
0129 }
0130 
0131 bool OneMessage::eq(const AbstractData &other) const
0132 {
0133     try {
0134         const OneMessage &o = dynamic_cast<const OneMessage &>(other);
0135         return o.mediaType == mediaType && mediaSubType == o.mediaSubType &&
0136                bodyFldParam == o.bodyFldParam && bodyFldId == o.bodyFldId &&
0137                bodyFldDesc == o.bodyFldDesc && bodyFldEnc == o.bodyFldEnc &&
0138                bodyFldOctets == o.bodyFldOctets && bodyFldMd5 == o.bodyFldMd5 &&
0139                bodyFldDsp == o.bodyFldDsp && bodyFldLang == o.bodyFldLang &&
0140                bodyFldLoc == o.bodyFldLoc && bodyExtension == o.bodyExtension;
0141     } catch (std::bad_cast &) {
0142         return false;
0143     }
0144 }
0145 
0146 bool TextMessage::eq(const AbstractData &other) const
0147 {
0148     try {
0149         const TextMessage &o = dynamic_cast<const TextMessage &>(other);
0150         return OneMessage::eq(o) && bodyFldLines == o.bodyFldLines;
0151     } catch (std::bad_cast &) {
0152         return false;
0153     }
0154 }
0155 
0156 QTextStream &TextMessage::dump(QTextStream &s, const int indent) const
0157 {
0158     QByteArray i(indent + 1, ' ');
0159     QByteArray lf("\n");
0160 
0161     return s << QByteArray(indent, ' ') << "TextMessage( " << mediaType << "/" << mediaSubType << lf <<
0162            i << "body-fld-param: " << bodyFldParam << lf <<
0163            i << "body-fld-id: " << bodyFldId << lf <<
0164            i << "body-fld-desc: " << bodyFldDesc << lf <<
0165            i << "body-fld-enc: " << bodyFldEnc << lf <<
0166            i << "body-fld-octets: " << bodyFldOctets << lf <<
0167            i << "bodyFldMd5: " << bodyFldMd5 << lf <<
0168            i << "body-fld-dsp: " << bodyFldDsp << lf <<
0169            i << "body-fld-lang: " << bodyFldLang << lf <<
0170            i << "body-fld-loc: " << bodyFldLoc << lf <<
0171            i << "body-extension is " << bodyExtension.typeName() << lf <<
0172            i << "body-fld-lines: " << bodyFldLines << lf <<
0173            QByteArray(indent, ' ') << ")";
0174     // FIXME: operator<< for QVariant...
0175 }
0176 
0177 bool MsgMessage::eq(const AbstractData &other) const
0178 {
0179     try {
0180         const MsgMessage &o = dynamic_cast<const MsgMessage &>(other);
0181         if (o.body) {
0182             if (body) {
0183                 if (*body != *o.body) {
0184                     return false;
0185                 }
0186             } else {
0187                 return false;
0188             }
0189         } else if (body) {
0190             return false;
0191         }
0192 
0193         return OneMessage::eq(o) && bodyFldLines == o.bodyFldLines &&
0194                envelope == o.envelope;
0195 
0196     } catch (std::bad_cast &) {
0197         return false;
0198     }
0199 }
0200 
0201 QTextStream &MsgMessage::dump(QTextStream &s, const int indent) const
0202 {
0203     QByteArray i(indent + 1, ' ');
0204     QByteArray lf("\n");
0205 
0206     s << QByteArray(indent, ' ') << "MsgMessage(" << lf;
0207     envelope.dump(s, indent + 1);
0208     s <<
0209       i << "body-fld-lines " << bodyFldLines << lf <<
0210       i << "body:" << lf;
0211 
0212     s <<
0213       i << "body-fld-param: " << bodyFldParam << lf <<
0214       i << "body-fld-id: " << bodyFldId << lf <<
0215       i << "body-fld-desc: " << bodyFldDesc << lf <<
0216       i << "body-fld-enc: " << bodyFldEnc << lf <<
0217       i << "body-fld-octets: " << bodyFldOctets << lf <<
0218       i << "bodyFldMd5: " << bodyFldMd5 << lf <<
0219       i << "body-fld-dsp: " << bodyFldDsp << lf <<
0220       i << "body-fld-lang: " << bodyFldLang << lf <<
0221       i << "body-fld-loc: " << bodyFldLoc << lf <<
0222       i << "body-extension is " << bodyExtension.typeName() << lf;
0223 
0224     if (body)
0225         body->dump(s, indent + 2);
0226     else
0227         s << i << " (null)";
0228     return s << lf << QByteArray(indent, ' ') << ")";
0229 }
0230 
0231 QTextStream &BasicMessage::dump(QTextStream &s, const int indent) const
0232 {
0233     QByteArray i(indent + 1, ' ');
0234     QByteArray lf("\n");
0235 
0236     return s << QByteArray(indent, ' ') << "BasicMessage( " << mediaType << "/" << mediaSubType << lf <<
0237            i << "body-fld-param: " << bodyFldParam << lf <<
0238            i << "body-fld-id: " << bodyFldId << lf <<
0239            i << "body-fld-desc: " << bodyFldDesc << lf <<
0240            i << "body-fld-enc: " << bodyFldEnc << lf <<
0241            i << "body-fld-octets: " << bodyFldOctets << lf <<
0242            i << "bodyFldMd5: " << bodyFldMd5 << lf <<
0243            i << "body-fld-dsp: " << bodyFldDsp << lf <<
0244            i << "body-fld-lang: " << bodyFldLang << lf <<
0245            i << "body-fld-loc: " << bodyFldLoc << lf <<
0246            i << "body-extension is " << bodyExtension.typeName() << lf <<
0247            QByteArray(indent, ' ') << ")";
0248     // FIXME: operator<< for QVariant...
0249 }
0250 
0251 bool MultiMessage::eq(const AbstractData &other) const
0252 {
0253     try {
0254         const MultiMessage &o = dynamic_cast<const MultiMessage &>(other);
0255 
0256         if (bodies.count() != o.bodies.count()) {
0257             return false;
0258         }
0259 
0260         for (int i = 0; i < bodies.count(); ++i) {
0261             if (bodies[i]) {
0262                 if (o.bodies[i]) {
0263                     if (*bodies[i] != *o.bodies[i]) {
0264                         return false;
0265                     }
0266                 } else {
0267                     return false;
0268                 }
0269             } else if (! o.bodies[i]) {
0270                 return false;
0271             }
0272         }
0273 
0274         return mediaSubType == o.mediaSubType && bodyFldParam == o.bodyFldParam &&
0275                bodyFldDsp == o.bodyFldDsp && bodyFldLang == o.bodyFldLang &&
0276                bodyFldLoc == o.bodyFldLoc && bodyExtension == o.bodyExtension;
0277 
0278     } catch (std::bad_cast &) {
0279         return false;
0280     }
0281 }
0282 
0283 QTextStream &MultiMessage::dump(QTextStream &s, const int indent) const
0284 {
0285     QByteArray i(indent + 1, ' ');
0286     QByteArray lf("\n");
0287 
0288     s << QByteArray(indent, ' ') << "MultiMessage( multipart/" << mediaSubType << lf <<
0289       i << "body-fld-param " << bodyFldParam << lf <<
0290       i << "body-fld-dsp " << bodyFldDsp << lf <<
0291       i << "body-fld-lang " << bodyFldLang << lf <<
0292       i << "body-fld-loc " << bodyFldLoc << lf <<
0293       i << "bodyExtension is " << bodyExtension.typeName() << lf <<
0294       i << "bodies: [ " << lf;
0295 
0296     for (QList<QSharedPointer<AbstractMessage> >::const_iterator it = bodies.begin(); it != bodies.end(); ++it)
0297         if (*it) {
0298             (**it).dump(s, indent + 2);
0299             s << lf;
0300         } else
0301             s << i << " (null)" << lf;
0302 
0303     return s << QByteArray(indent, ' ') << "] )";
0304 }
0305 
0306 AbstractMessage::bodyFldParam_t AbstractMessage::makeBodyFldParam(const QVariant &input, const QByteArray &line, const int start)
0307 {
0308     bodyFldParam_t map;
0309     if (input.type() != QVariant::List) {
0310         if (input.type() == QVariant::ByteArray && input.toByteArray().isNull())
0311             return map;
0312         throw UnexpectedHere("body-fld-param: not a list / nil", line, start);
0313     }
0314     QVariantList list = input.toList();
0315     if (list.size() % 2)
0316         throw UnexpectedHere("body-fld-param: wrong number of entries", line, start);
0317     for (int j = 0; j < list.size(); j += 2)
0318         if (list[j].type() != QVariant::ByteArray || list[j+1].type() != QVariant::ByteArray)
0319             throw UnexpectedHere("body-fld-param: string not found", line, start);
0320         else
0321             map[ list[j].toByteArray().toUpper() ] = list[j+1].toByteArray();
0322     return map;
0323 }
0324 
0325 AbstractMessage::bodyFldDsp_t AbstractMessage::makeBodyFldDsp(const QVariant &input, const QByteArray &line, const int start)
0326 {
0327     bodyFldDsp_t res;
0328 
0329     if (input.type() != QVariant::List) {
0330         if (input.type() == QVariant::ByteArray) {
0331             if (input.toByteArray().isNull()) {
0332                 return res;
0333             } else {
0334                 qDebug() << "IMAP Parser warning: body-fld-dsp not a list or nil, got this instead: " << input.toByteArray();
0335                 return res;
0336             }
0337         }
0338         throw UnexpectedHere("body-fld-dsp: not a list / nil", line, start);
0339     }
0340     QVariantList list = input.toList();
0341 
0342     if (list.size() < 1) {
0343         throw ParseError("body-fld-dsp: empty list is not allowed", line, start);
0344     }
0345     if (list[0].type() != QVariant::ByteArray) {
0346         throw UnexpectedHere("body-fld-dsp: first item is not a string", line, start);
0347     }
0348     res.first = list[0].toByteArray();
0349     if (list.size() > 2) {
0350         throw ParseError("body-fld-dsp: too many items in the list", line, start);
0351     } else if (list.size() == 2) {
0352         res.second = makeBodyFldParam(list[1], line, start);
0353     } else {
0354         qDebug() << "IMAP Parser warning: body-fld-dsp: second item not present, ignoring";
0355     }
0356     return res;
0357 }
0358 
0359 QList<QByteArray> AbstractMessage::makeBodyFldLang(const QVariant &input, const QByteArray &line, const int start)
0360 {
0361     QList<QByteArray> res;
0362     if (input.type() == QVariant::ByteArray) {
0363         if (input.toByteArray().isNull())   // handle NIL
0364             return res;
0365         res << input.toByteArray();
0366     } else if (input.type() == QVariant::List) {
0367         QVariantList list = input.toList();
0368         for (QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it)
0369             if (it->type() != QVariant::ByteArray)
0370                 throw UnexpectedHere("body-fld-lang has wrong structure", line, start);
0371             else
0372                 res << it->toByteArray();
0373     } else
0374         throw UnexpectedHere("body-fld-lang not found", line, start);
0375     return res;
0376 }
0377 
0378 uint AbstractMessage::extractUInt(const QVariant &var, const QByteArray &line, const int start)
0379 {
0380     if (var.type() == QVariant::UInt)
0381         return var.toUInt();
0382     if (var.type() == QVariant::ByteArray) {
0383         bool ok = false;
0384         int number = var.toInt(&ok);
0385         if (ok) {
0386             if (number >= 0) {
0387                 return number;
0388             } else {
0389                 qDebug() << "Parser warning:" << number << "is not an unsigned int";
0390                 return 0;
0391             }
0392         } else if (var.toByteArray().isEmpty()) {
0393             qDebug() << "Parser warning: expected unsigned int, but got NIL or an empty string instead, yuck";
0394             return 0;
0395         } else {
0396             throw UnexpectedHere("extractUInt: not a number", line, start);
0397         }
0398     }
0399     throw UnexpectedHere("extractUInt: weird data type", line, start);
0400 }
0401 
0402 quint64 AbstractMessage::extractUInt64(const QVariant &var, const QByteArray &line, const int start)
0403 {
0404     if (var.type() == QVariant::ULongLong)
0405         return var.toULongLong();
0406     if (var.type() == QVariant::ByteArray) {
0407         bool ok = false;
0408         qint64 number = var.toLongLong(&ok);
0409         if (ok) {
0410             if (number >= 0) {
0411                 return number;
0412             } else {
0413                 qDebug() << "Parser warning:" << number << "is not an unsigned 64 bit int";
0414                 return 0;
0415             }
0416         } else if (var.toByteArray().isEmpty()) {
0417             qDebug() << "Parser warning: expected unsigned 64 bit int, but got NIL or an empty string instead, yuck";
0418             return 0;
0419         } else {
0420             throw UnexpectedHere("extractUInt64: not a number", line, start);
0421         }
0422     }
0423     throw UnexpectedHere("extractUInt64: weird data type", line, start);
0424 }
0425 
0426 
0427 QSharedPointer<AbstractMessage> AbstractMessage::fromList(const QVariantList &items, const QByteArray &line, const int start)
0428 {
0429     if (items.size() < 2)
0430         throw NoData("AbstractMessage::fromList: no data", line, start);
0431 
0432     if (items[0].type() == QVariant::ByteArray) {
0433         // it's a single-part message, hurray
0434 
0435         int i = 0;
0436         QByteArray mediaType = items[i].toByteArray().toLower();
0437         ++i;
0438         QByteArray mediaSubType = items[i].toByteArray().toLower();
0439         ++i;
0440 
0441         if (items.size() < 7) {
0442             qDebug() << "AbstractMessage::fromList(): body-type-basic(?): yuck, too few items, using what we've got";
0443         }
0444 
0445         bodyFldParam_t bodyFldParam;
0446         if (i < items.size()) {
0447             bodyFldParam = makeBodyFldParam(items[i], line, start);
0448             ++i;
0449         }
0450 
0451         QByteArray bodyFldId;
0452         if (i < items.size()) {
0453             if (items[i].type() != QVariant::ByteArray)
0454                 throw UnexpectedHere("body-fld-id not recognized as a ByteArray", line, start);
0455             bodyFldId = items[i].toByteArray();
0456             ++i;
0457         }
0458 
0459         QByteArray bodyFldDesc;
0460         if (i < items.size()) {
0461             if (items[i].type() != QVariant::ByteArray)
0462                 throw UnexpectedHere("body-fld-desc not recognized as a ByteArray", line, start);
0463             bodyFldDesc = items[i].toByteArray();
0464             ++i;
0465         }
0466 
0467         QByteArray bodyFldEnc;
0468         if (i < items.size()) {
0469             if (items[i].type() != QVariant::ByteArray)
0470                 throw UnexpectedHere("body-fld-enc not recognized as a ByteArray", line, start);
0471             bodyFldEnc = items[i].toByteArray();
0472             ++i;
0473         }
0474 
0475         quint64 bodyFldOctets = 0;
0476         if (i < items.size()) {
0477             bodyFldOctets = extractUInt64(items[i], line, start);
0478             ++i;
0479         }
0480 
0481         uint bodyFldLines = 0;
0482         Envelope envelope;
0483         QSharedPointer<AbstractMessage> body;
0484 
0485         // This is used when the IMAP response fails to parse in a catastrophic manner. It's better than refusing
0486         // to work with that mailbox altogether. In future, we might get a client-side MIME Parser for this :).
0487         // In the meanwhile, it's critical to override the MIME type properly so that the upper layers don't assert
0488         // on, e.g., missing headers (or invalid indexes in there), etc.
0489 #define RETURN_ERROR_BINARY_PART \
0490     qDebug() << "will return a fake raw part instead of a damaged" << QByteArray(mediaType + '/' + mediaSubType).data() << "part"; \
0491     bodyFldParam["x-trojita-original-mime-type"] = mediaType + '/' + mediaSubType; \
0492     return QSharedPointer<AbstractMessage>(new BasicMessage("application", "x-trojita-malformed-part-from-imap-response", \
0493         bodyFldParam, bodyFldId, bodyFldDesc, bodyFldEnc, bodyFldOctets, \
0494         QByteArray(), bodyFldDsp_t(), QList<QByteArray>(), QByteArray(), QVariant()))
0495 
0496         enum { MESSAGE, TEXT, BASIC} kind;
0497 
0498         if (mediaType == "message" && mediaSubType == "rfc822") {
0499             // extract envelope, body, body-fld-lines
0500 
0501             if (items.size() < 10)
0502                 throw NoData("too few fields for a Message-message", line, start);
0503 
0504             kind = MESSAGE;
0505             if (items[i].type() == QVariant::ByteArray && items[i].toByteArray().isEmpty()) {
0506                 // ENVELOPE is NIL -- a server bug, but there's a chance that perhaps the body might still be readable...
0507                 qDebug() << "message/rfc822: yuck, got NIL for envelope";
0508             } else if (items[i].type() != QVariant::List) {
0509                 qDebug() << "message/rfc822: yuck, ENVELOPE is not a list";
0510                 RETURN_ERROR_BINARY_PART;
0511             } else {
0512                 envelope = Envelope::fromList(items[i].toList(), line, start);
0513             }
0514             ++i;
0515 
0516             if (items[i].type() != QVariant::List) {
0517                 // we're screwed, let's fall back to a binary part rendering
0518                 qDebug() << "message/rfc822: yuck, got garbage for BODY";
0519                 RETURN_ERROR_BINARY_PART;
0520             } else {
0521                 body = AbstractMessage::fromList(items[i].toList(), line, start);
0522             }
0523             ++i;
0524 
0525             try {
0526                 bodyFldLines = extractUInt(items[i], line, start);
0527             } catch (const UnexpectedHere &) {
0528                 qDebug() << "message/rfc822: yuck, invalid body-fld-lines";
0529             }
0530             ++i;
0531 
0532         } else if (mediaType == "text") {
0533             kind = TEXT;
0534             if (i < items.size()) {
0535                 // extract body-fld-lines
0536                 bodyFldLines = extractUInt(items[i], line, start);
0537                 ++i;
0538             }
0539         } else {
0540             // don't extract anything as we're done here
0541             kind = BASIC;
0542         }
0543 
0544         // extract body-ext-1part
0545 
0546         // body-fld-md5
0547         QByteArray bodyFldMd5;
0548         if (i < items.size()) {
0549             if (items[i].type() != QVariant::ByteArray)
0550                 throw UnexpectedHere("body-fld-md5 not a ByteArray", line, start);
0551             bodyFldMd5 = items[i].toByteArray();
0552             ++i;
0553         }
0554 
0555         // body-fld-dsp
0556         bodyFldDsp_t bodyFldDsp;
0557         if (i < items.size()) {
0558             bodyFldDsp = makeBodyFldDsp(items[i], line, start);
0559             ++i;
0560         }
0561 
0562         // body-fld-lang
0563         QList<QByteArray> bodyFldLang;
0564         if (i < items.size()) {
0565             bodyFldLang = makeBodyFldLang(items[i], line, start);
0566             ++i;
0567         }
0568 
0569         // body-fld-loc
0570         QByteArray bodyFldLoc;
0571         if (i < items.size()) {
0572             if (items[i].type() != QVariant::ByteArray)
0573                 throw UnexpectedHere("body-fld-loc not found", line, start);
0574             bodyFldLoc = items[i].toByteArray();
0575             ++i;
0576         }
0577 
0578         // body-extension
0579         QVariant bodyExtension;
0580         if (i < items.size()) {
0581             if (i == items.size() - 1) {
0582                 bodyExtension = items[i];
0583                 ++i;
0584             } else {
0585                 QVariantList list;
0586                 for (; i < items.size(); ++i)
0587                     list << items[i];
0588                 bodyExtension = list;
0589             }
0590         }
0591 
0592         switch (kind) {
0593         case MESSAGE:
0594             return QSharedPointer<AbstractMessage>(
0595                        new MsgMessage(mediaType, mediaSubType, bodyFldParam,
0596                                       bodyFldId, bodyFldDesc, bodyFldEnc, bodyFldOctets,
0597                                       bodyFldMd5, bodyFldDsp, bodyFldLang, bodyFldLoc,
0598                                       bodyExtension, envelope, body, bodyFldLines)
0599                    );
0600         case TEXT:
0601             return QSharedPointer<AbstractMessage>(
0602                        new TextMessage(mediaType, mediaSubType, bodyFldParam,
0603                                        bodyFldId, bodyFldDesc, bodyFldEnc, bodyFldOctets,
0604                                        bodyFldMd5, bodyFldDsp, bodyFldLang, bodyFldLoc,
0605                                        bodyExtension, bodyFldLines)
0606                    );
0607         case BASIC:
0608         default:
0609             return QSharedPointer<AbstractMessage>(
0610                        new BasicMessage(mediaType, mediaSubType, bodyFldParam,
0611                                         bodyFldId, bodyFldDesc, bodyFldEnc, bodyFldOctets,
0612                                         bodyFldMd5, bodyFldDsp, bodyFldLang, bodyFldLoc,
0613                                         bodyExtension)
0614                    );
0615         }
0616 
0617     } else if (items[0].type() == QVariant::List) {
0618 
0619         if (items.size() < 2)
0620             throw ParseError("body-type-mpart: structure should be \"body* string\"", line, start);
0621 
0622         int i = 0;
0623 
0624         QList<QSharedPointer<AbstractMessage> > bodies;
0625         while (items[i].type() == QVariant::List) {
0626             bodies << fromList(items[i].toList(), line, start);
0627             ++i;
0628         }
0629 
0630         if (items[i].type() != QVariant::ByteArray)
0631             throw UnexpectedHere("body-type-mpart: media-subtype not recognized", line, start);
0632         QByteArray mediaSubType = items[i].toByteArray().toLower();
0633         ++i;
0634 
0635         // body-ext-mpart
0636 
0637         // body-fld-param
0638         bodyFldParam_t bodyFldParam;
0639         if (i < items.size()) {
0640             bodyFldParam = makeBodyFldParam(items[i], line, start);
0641             ++i;
0642         }
0643 
0644         // body-fld-dsp
0645         bodyFldDsp_t bodyFldDsp;
0646         if (i < items.size()) {
0647             bodyFldDsp = makeBodyFldDsp(items[i], line, start);
0648             ++i;
0649         }
0650 
0651         // body-fld-lang
0652         QList<QByteArray> bodyFldLang;
0653         if (i < items.size()) {
0654             bodyFldLang = makeBodyFldLang(items[i], line, start);
0655             ++i;
0656         }
0657 
0658         // body-fld-loc
0659         QByteArray bodyFldLoc;
0660         if (i < items.size()) {
0661             if (items[i].type() != QVariant::ByteArray)
0662                 throw UnexpectedHere("body-fld-loc not found", line, start);
0663             bodyFldLoc = items[i].toByteArray();
0664             ++i;
0665         }
0666 
0667         // body-extension
0668         QVariant bodyExtension;
0669         if (i < items.size()) {
0670             if (i == items.size() - 1) {
0671                 bodyExtension = items[i];
0672                 ++i;
0673             } else {
0674                 QVariantList list;
0675                 for (; i < items.size(); ++i)
0676                     list << items[i];
0677                 bodyExtension = list;
0678             }
0679         }
0680 
0681         return QSharedPointer<AbstractMessage>(
0682                    new MultiMessage(bodies, mediaSubType, bodyFldParam,
0683                                     bodyFldDsp, bodyFldLang, bodyFldLoc, bodyExtension));
0684     } else {
0685         throw UnexpectedHere("AbstractMessage::fromList: invalid data type of first item", line, start);
0686     }
0687 }
0688 
0689 void dumpListOfAddresses(QTextStream &stream, const QList<MailAddress> &list, const int indent)
0690 {
0691     QByteArray lf("\n");
0692     switch (list.size()) {
0693     case 0:
0694         stream << "[ ]" << lf;
0695         break;
0696     case 1:
0697         stream << "[ " << list.front() << " ]" << lf;
0698         break;
0699     default: {
0700         QByteArray i(indent + 1, ' ');
0701         stream << "[" << lf;
0702         for (QList<MailAddress>::const_iterator it = list.begin(); it != list.end(); ++it)
0703             stream << i << *it << lf;
0704         stream << QByteArray(indent, ' ') << "]" << lf;
0705     }
0706     }
0707 }
0708 
0709 QTextStream &Envelope::dump(QTextStream &stream, const int indent) const
0710 {
0711     QByteArray i(indent + 1, ' ');
0712     QByteArray lf("\n");
0713     stream << QByteArray(indent, ' ') << "Envelope(" << lf <<
0714            i << "Date: " << date.toString() << lf <<
0715            i << "Subject: " << subject << lf;
0716     stream << i << "From: "; dumpListOfAddresses(stream, from, indent + 1);
0717     stream << i << "Sender: "; dumpListOfAddresses(stream, sender, indent + 1);
0718     stream << i << "Reply-To: "; dumpListOfAddresses(stream, replyTo, indent + 1);
0719     stream << i << "To: "; dumpListOfAddresses(stream, to, indent + 1);
0720     stream << i << "Cc: "; dumpListOfAddresses(stream, cc, indent + 1);
0721     stream << i << "Bcc: "; dumpListOfAddresses(stream, bcc, indent + 1);
0722     stream <<
0723            i << "In-Reply-To: " << inReplyTo << lf <<
0724            i << "Message-Id: " << messageId << lf;
0725     return stream << QByteArray(indent, ' ') << ")" << lf;
0726 }
0727 
0728 QTextStream &operator<<(QTextStream &stream, const Envelope &e)
0729 {
0730     return e.dump(stream, 0);
0731 }
0732 
0733 QTextStream &operator<<(QTextStream &stream, const AbstractMessage::bodyFldParam_t &p)
0734 {
0735     stream << "bodyFldParam[ ";
0736     bool first = true;
0737     for (AbstractMessage::bodyFldParam_t::const_iterator it = p.begin(); it != p.end(); ++it, first = false)
0738         stream << (first ? "" : ", ") << it.key() << ": " << it.value();
0739     return stream << "]";
0740 }
0741 
0742 QTextStream &operator<<(QTextStream &stream, const AbstractMessage::bodyFldDsp_t &p)
0743 {
0744     return stream << "bodyFldDsp( " << p.first << ", " << p.second << ")";
0745 }
0746 
0747 QTextStream &operator<<(QTextStream &stream, const QList<QByteArray> &list)
0748 {
0749     stream << "( ";
0750     bool first = true;
0751     for (QList<QByteArray>::const_iterator it = list.begin(); it != list.end(); ++it, first = false)
0752         stream << (first ? "" : ", ") << *it;
0753     return stream << " )";
0754 }
0755 
0756 bool operator==(const Envelope &a, const Envelope &b)
0757 {
0758     return a.date == b.date && a.subject == b.subject &&
0759            a.from == b.from && a.sender == b.sender && a.replyTo == b.replyTo &&
0760            a.to == b.to && a.cc == b.cc && a.bcc == b.bcc &&
0761            a.inReplyTo == b.inReplyTo && a.messageId == b.messageId;
0762 }
0763 
0764 /** @short Extract interesting part-specific metadata from the BODYSTRUCTURE into the actual part
0765 
0766 Examples are stuff like the charset, or the suggested filename.
0767 */
0768 void AbstractMessage::storeInterestingFields(Mailbox::TreeItemPart *p) const
0769 {
0770     p->setBodyFldParam(bodyFldParam);
0771 
0772     // Charset
0773     bodyFldParam_t::const_iterator it = bodyFldParam.find("CHARSET");
0774     if (it != bodyFldParam.end()) {
0775         p->setCharset(*it);
0776     }
0777 
0778     // Support for format=flowed, RFC3676
0779     it = bodyFldParam.find("FORMAT");
0780     if (it != bodyFldParam.end()) {
0781         p->setContentFormat(*it);
0782 
0783         it = bodyFldParam.find("DELSP");
0784         if (it != bodyFldParam.end()) {
0785             p->setContentDelSp(*it);
0786         }
0787     }
0788 
0789     // Filename and content-disposition
0790     if (!bodyFldDsp.first.isNull()) {
0791         p->setBodyDisposition(bodyFldDsp.first);
0792         p->setFileName(Imap::extractRfc2231Param(bodyFldDsp.second, "FILENAME"));
0793     }
0794     // Try to look for the obsolete "name" right in the Content-Type header (as parsed by the IMAP server) as a fallback
0795     // As per Thomas' suggestion, an empty-but-specified filename is happily overwritten here by design.
0796     if (p->fileName().isEmpty()) {
0797         p->setFileName(Imap::extractRfc2231Param(bodyFldParam, "NAME"));
0798     }
0799 }
0800 
0801 void OneMessage::storeInterestingFields(Mailbox::TreeItemPart *p) const
0802 {
0803     AbstractMessage::storeInterestingFields(p);
0804     p->setTransferEncoding(bodyFldEnc.toLower());
0805     p->setOctets(bodyFldOctets);
0806     p->setBodyFldId(bodyFldId);
0807 }
0808 
0809 void MultiMessage::storeInterestingFields(Mailbox::TreeItemPart *p) const
0810 {
0811     AbstractMessage::storeInterestingFields(p);
0812 
0813     // The multipart/related can specify the root part to show
0814     if (mediaSubType == "related") {
0815         bodyFldParam_t::const_iterator it = bodyFldParam.find("START");
0816         if (it != bodyFldParam.end()) {
0817             p->setMultipartRelatedStartPart(*it);
0818         }
0819     }
0820 }
0821 
0822 Mailbox::TreeItemChildrenList TextMessage::createTreeItems(Mailbox::TreeItem *parent) const
0823 {
0824     Mailbox::TreeItemChildrenList list;
0825     Mailbox::TreeItemPart *p = new Mailbox::TreeItemPart(parent, mediaType + "/" + mediaSubType);
0826     storeInterestingFields(p);
0827     list << p;
0828     return list;
0829 }
0830 
0831 Mailbox::TreeItemChildrenList BasicMessage::createTreeItems(Mailbox::TreeItem *parent) const
0832 {
0833     Mailbox::TreeItemChildrenList list;
0834     Mailbox::TreeItemPart *p = new Mailbox::TreeItemPart(parent, mediaType + "/" + mediaSubType);
0835     storeInterestingFields(p);
0836     list << p;
0837     return list;
0838 }
0839 
0840 Mailbox::TreeItemChildrenList MsgMessage::createTreeItems(Mailbox::TreeItem *parent) const
0841 {
0842     Mailbox::TreeItemChildrenList list;
0843     Mailbox::TreeItemPart *part = new Mailbox::TreeItemPartMultipartMessage(parent, envelope);
0844     part->setChildren(body->createTreeItems(part));     // always returns an empty list -> no need to qDeleteAll()
0845     storeInterestingFields(part);
0846     list << part;
0847     return list;
0848 }
0849 
0850 Mailbox::TreeItemChildrenList MultiMessage::createTreeItems(Mailbox::TreeItem *parent) const
0851 {
0852     Mailbox::TreeItemChildrenList list, list2;
0853     Mailbox::TreeItemPart *part = new Mailbox::TreeItemPart(parent, "multipart/" + mediaSubType);
0854     for (QList<QSharedPointer<AbstractMessage> >::const_iterator it = bodies.begin(); it != bodies.end(); ++it) {
0855         list2 << (*it)->createTreeItems(part);
0856     }
0857     part->setChildren(list2);   // always returns an empty list -> no need to qDeleteAll()
0858     storeInterestingFields(part);
0859     list << part;
0860     return list;
0861 }
0862 
0863 }
0864 }
0865 
0866 QDebug operator<<(QDebug dbg, const Imap::Message::Envelope &envelope)
0867 {
0868     using namespace Imap::Message;
0869     return dbg << "Envelope( FROM" << MailAddress::prettyList(envelope.from, MailAddress::FORMAT_READABLE) <<
0870            "TO" << MailAddress::prettyList(envelope.to, MailAddress::FORMAT_READABLE) <<
0871            "CC" << MailAddress::prettyList(envelope.cc, MailAddress::FORMAT_READABLE) <<
0872            "BCC" << MailAddress::prettyList(envelope.bcc, MailAddress::FORMAT_READABLE) <<
0873            "SUBJECT" << envelope.subject <<
0874            "DATE" << envelope.date <<
0875            "MESSAGEID" << envelope.messageId;
0876 }
0877 
0878 QDataStream &operator>>(QDataStream &stream, Imap::Message::Envelope &e)
0879 {
0880     return stream >> e.bcc >> e.cc >> e.date >> e.from >> e.inReplyTo >>
0881            e.messageId >> e.replyTo >> e.sender >> e.subject >> e.to;
0882 }
0883 
0884 QDataStream &operator<<(QDataStream &stream, const Imap::Message::Envelope &e)
0885 {
0886     return stream << e.bcc << e.cc << e.date << e.from << e.inReplyTo <<
0887            e.messageId << e.replyTo << e.sender << e.subject << e.to;
0888 }