File indexing completed on 2023-09-24 09:25:00

0001 /*
0002     kmime_content.cpp
0003 
0004     KMime, the KDE Internet mail/usenet news message library.
0005     SPDX-FileCopyrightText: 2001 the KMime authors.
0006     See file AUTHORS for details
0007     SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
0008     SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 /**
0013   @file
0014   This file is part of the API for handling @ref MIME data and
0015   defines the Content class.
0016 
0017   @brief
0018   Defines the Content class.
0019 
0020   @authors the KMime authors (see AUTHORS file),
0021   Volker Krause \<vkrause@kde.org\>
0022 */
0023 #include "kmime_content.h"
0024 #include "kmime_content_p.h"
0025 #include "kmime_message.h"
0026 #include "kmime_header_parsing.h"
0027 #include "kmime_header_parsing_p.h"
0028 #include "kmime_parsers.h"
0029 #include "kmime_util_p.h"
0030 
0031 #include <KCodecs>
0032 
0033 #include <QDebug>
0034 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0035 #include <QStringDecoder>
0036 #endif
0037 #include <QTextCodec>
0038 
0039 using namespace KMime;
0040 
0041 namespace KMime
0042 {
0043 
0044 Content::Content(Content *parent)
0045     : d_ptr(new ContentPrivate)
0046 {
0047     d_ptr->parent = parent;
0048 }
0049 
0050 Content::~Content()
0051 {
0052     Q_D(Content);
0053     qDeleteAll(d->headers);
0054     d->headers.clear();
0055     delete d_ptr;
0056     d_ptr = nullptr;
0057 }
0058 
0059 bool Content::hasContent() const
0060 {
0061     return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents().isEmpty();
0062 }
0063 
0064 void Content::setContent(const QByteArray &s)
0065 {
0066     Q_D(Content);
0067     KMime::HeaderParsing::extractHeaderAndBody(s, d->head, d->body);
0068 }
0069 
0070 QByteArray Content::head() const
0071 {
0072     return d_ptr->head;
0073 }
0074 
0075 void Content::setHead(const QByteArray &head)
0076 {
0077     d_ptr->head = head;
0078     if (!head.endsWith('\n')) {
0079         d_ptr->head += '\n';
0080     }
0081 }
0082 
0083 QByteArray Content::body() const
0084 {
0085     return d_ptr->body;
0086 }
0087 
0088 void Content::setBody(const QByteArray &body)
0089 {
0090     d_ptr->body = body;
0091 }
0092 
0093 QByteArray Content::preamble() const
0094 {
0095     return d_ptr->preamble;
0096 }
0097 
0098 void Content::setPreamble(const QByteArray &preamble)
0099 {
0100     d_ptr->preamble = preamble;
0101 }
0102 
0103 QByteArray Content::epilogue() const
0104 {
0105     return d_ptr->epilogue;
0106 }
0107 
0108 void Content::setEpilogue(const QByteArray &epilogue)
0109 {
0110     d_ptr->epilogue = epilogue;
0111 }
0112 
0113 void Content::parse()
0114 {
0115     Q_D(Content);
0116 
0117     // Clean up old headers and parse them again.
0118     qDeleteAll(d->headers);
0119     d->headers.clear();
0120     d->headers = HeaderParsing::parseHeaders(d->head);
0121 
0122     // If we are frozen, save the body as-is. This is done because parsing
0123     // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.)
0124     if (d->frozen) {
0125         d->frozenBody = d->body;
0126     }
0127 
0128     // Clean up old sub-Contents and parse them again.
0129     qDeleteAll(d->multipartContents);
0130     d->multipartContents.clear();
0131     d->clearBodyMessage();
0132     Headers::ContentType *ct = contentType();
0133     if (ct->isEmpty()) { //Set default content-type as defined in https://tools.ietf.org/html/rfc2045#page-10 (5.2.  Content-Type Defaults)
0134         ct->setMimeType("text/plain");
0135         ct->setCharset("us-ascii");
0136     }
0137     if (ct->isText()) {
0138         // This content is either text, or of unknown type.
0139 
0140         if (d->parseUuencoded(this)) {
0141             // This is actually uuencoded content generated by broken software.
0142         } else if (d->parseYenc(this)) {
0143             // This is actually yenc content generated by broken software.
0144         } else {
0145             // This is just plain text.
0146         }
0147     } else if (ct->isMultipart()) {
0148         // This content claims to be MIME multipart.
0149 
0150         if (d->parseMultipart(this)) {
0151             // This is actual MIME multipart content.
0152         } else {
0153             // Parsing failed; treat this content as "text/plain".
0154             ct->setMimeType("text/plain");
0155             ct->setCharset("US-ASCII");
0156         }
0157     } else {
0158         // This content is something else, like an encapsulated message or a binary attachment
0159         // or something like that
0160         if (bodyIsMessage()) {
0161             d->bodyAsMessage = Message::Ptr(new Message);
0162             d->bodyAsMessage->setContent(d->body);
0163             d->bodyAsMessage->setFrozen(d->frozen);
0164             d->bodyAsMessage->parse();
0165             d->bodyAsMessage->d_ptr->parent = this;
0166 
0167             // Clear the body, as it is now represented by d->bodyAsMessage. This is the same behavior
0168             // as with multipart contents, since parseMultipart() clears the body as well
0169             d->body.clear();
0170         }
0171     }
0172 }
0173 
0174 bool Content::isFrozen() const
0175 {
0176     return d_ptr->frozen;
0177 }
0178 
0179 void Content::setFrozen(bool frozen)
0180 {
0181     d_ptr->frozen = frozen;
0182 }
0183 
0184 void Content::assemble()
0185 {
0186     Q_D(Content);
0187     if (d->frozen) {
0188         return;
0189     }
0190 
0191     d->head = assembleHeaders();
0192     const auto contentsList = contents();
0193     for (Content *c : contentsList) {
0194         c->assemble();
0195     }
0196 }
0197 
0198 QByteArray Content::assembleHeaders()
0199 {
0200     Q_D(Content);
0201     QByteArray newHead;
0202     for (const Headers::Base *h : std::as_const(d->headers)) {
0203         if (!h->isEmpty()) {
0204             newHead += foldHeader(h->as7BitString()) + '\n';
0205         }
0206     }
0207 
0208     return newHead;
0209 }
0210 
0211 void Content::clear()
0212 {
0213     Q_D(Content);
0214     qDeleteAll(d->headers);
0215     d->headers.clear();
0216     clearContents();
0217     d->head.clear();
0218     d->body.clear();
0219 }
0220 
0221 void Content::clearContents(bool del)
0222 {
0223     Q_D(Content);
0224     if (del) {
0225         qDeleteAll(d->multipartContents);
0226     }
0227     d->multipartContents.clear();
0228     d->clearBodyMessage();
0229 }
0230 
0231 QByteArray Content::encodedContent(bool useCrLf)
0232 {
0233     QByteArray encodedContentData = head();           // return value; initialise with the head data
0234     const QByteArray encodedBodyData = encodedBody();
0235 
0236     /* Make sure that head and body have at least two newlines as separator, otherwise add one.
0237      * If we have enough newlines as sperator, then we should not change the number of newlines
0238      * to not break digital signatures
0239      */
0240     if (!encodedContentData.endsWith("\n\n") &&
0241         !encodedBodyData.startsWith("\n\n") &&
0242         !(encodedContentData.endsWith("\n") && encodedBodyData.startsWith("\n"))){
0243         encodedContentData += '\n';
0244     }
0245     encodedContentData += encodedBodyData;
0246 
0247     if (useCrLf) {
0248         return LFtoCRLF(encodedContentData);
0249     } else {
0250         return encodedContentData;
0251     }
0252 }
0253 
0254 QByteArray Content::encodedBody()
0255 {
0256     Q_D(Content);
0257     QByteArray e;
0258     // Body.
0259     if (d->frozen) {
0260         // This Content is frozen.
0261         if (d->frozenBody.isEmpty()) {
0262             // This Content has never been parsed.
0263             e += d->body;
0264         } else {
0265             // Use the body as it was before parsing.
0266             e += d->frozenBody;
0267         }
0268     } else if (bodyIsMessage() && d->bodyAsMessage) {
0269         // This is an encapsulated message
0270         // No encoding needed, as the ContentTransferEncoding can only be 7bit
0271         // for encapsulated messages
0272         e += d->bodyAsMessage->encodedContent();
0273     } else if (!d->body.isEmpty()) {
0274         // This is a single-part Content.
0275         Headers::ContentTransferEncoding *enc = contentTransferEncoding();
0276 
0277         if (enc->needToEncode()) {
0278             if (enc->encoding() == Headers::CEquPr) {
0279                 e += KCodecs::quotedPrintableEncode(d->body, false);
0280             } else {
0281                 QByteArray encoded;
0282                 KCodecs::base64Encode(d->body, encoded, true);
0283                 e += encoded;
0284                 e += '\n';
0285             }
0286         } else {
0287             e += d->body;
0288         }
0289     }
0290 
0291     if (!d->frozen && !d->multipartContents.isEmpty()) {
0292         // This is a multipart Content.
0293         Headers::ContentType *ct = contentType();
0294         QByteArray boundary = "\n--" + ct->boundary();
0295 
0296         if (!d->preamble.isEmpty()) {
0297             e += d->preamble;
0298         }
0299 
0300         //add all (encoded) contents separated by boundaries
0301         for (Content *c : std::as_const(d->multipartContents)) {
0302             e += boundary + '\n';
0303             e += c->encodedContent(false);    // don't convert LFs here, we do that later!!!!!
0304         }
0305         //finally append the closing boundary
0306         e += boundary + "--\n";
0307 
0308         if (!d->epilogue.isEmpty()) {
0309             e += d->epilogue;
0310         }
0311     }
0312     return e;
0313 }
0314 
0315 QByteArray Content::decodedContent()
0316 {
0317     QByteArray ret;
0318     Headers::ContentTransferEncoding *ec = contentTransferEncoding();
0319     bool removeTrailingNewline = false;
0320 
0321     if (d_ptr->body.isEmpty()) {
0322         return ret;
0323     }
0324 
0325     if (ec->isDecoded()) {
0326         ret = d_ptr->body;
0327         //Laurent Fix bug #311267
0328         //removeTrailingNewline = true;
0329     } else {
0330         switch (ec->encoding()) {
0331         case Headers::CEbase64 : {
0332             KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64");
0333             Q_ASSERT(codec);
0334             ret.resize(codec->maxDecodedSizeFor(d_ptr->body.size()));
0335             QScopedPointer<KCodecs::Decoder> decoder(codec->makeDecoder());
0336             QByteArray::const_iterator inputIt = d_ptr->body.constBegin();
0337             QByteArray::iterator resultIt = ret.begin();
0338             decoder->decode(inputIt, d_ptr->body.constEnd(), resultIt, ret.constEnd());
0339             ret.truncate(resultIt - ret.begin());
0340             break;
0341         }
0342         case Headers::CEquPr :
0343             ret = KCodecs::quotedPrintableDecode(d_ptr->body);
0344             removeTrailingNewline = true;
0345             break;
0346         case Headers::CEuuenc :
0347             KCodecs::uudecode(d_ptr->body, ret);
0348             break;
0349         case Headers::CEbinary :
0350             ret = d_ptr->body;
0351             removeTrailingNewline = false;
0352             break;
0353         default :
0354             ret = d_ptr->body;
0355             removeTrailingNewline = true;
0356         }
0357     }
0358 
0359     if (removeTrailingNewline && (ret.size() > 0) && (ret[ret.size() - 1] == '\n')) {
0360         ret.resize(ret.size() - 1);
0361     }
0362 
0363     return ret;
0364 }
0365 
0366 QString Content::decodedText(bool trimText, bool removeTrailingNewlines)
0367 {
0368     if (!d_ptr->decodeText(this)) {   //this is not a text content !!
0369       return {};
0370     }
0371 
0372 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0373     QTextCodec *codec = QTextCodec::codecForName(contentType()->charset());
0374     if (codec == nullptr) {   // no suitable codec found => try local settings and hope the best ;-)
0375         codec = QTextCodec::codecForLocale();
0376         QByteArray chset = codec->name();
0377         contentType()->setCharset(chset);
0378     }
0379 
0380     QString s = codec->toUnicode(d_ptr->body.data(), d_ptr->body.length());
0381 #else
0382     QStringDecoder codec(contentType()->charset().constData());
0383     if (!codec.isValid()) {   // no suitable codec found => try local settings and hope the best ;-)
0384         codec = QStringDecoder(QStringDecoder::System);
0385         QByteArray chset = codec.name();
0386         contentType()->setCharset(chset);
0387     }
0388 
0389     QString s = codec.decode(d_ptr->body);
0390 #endif
0391 
0392     if (trimText || removeTrailingNewlines) {
0393         int i;
0394         for (i = s.length() - 1; i >= 0; --i) {
0395             if (trimText) {
0396                 if (!s[i].isSpace()) {
0397                     break;
0398                 }
0399             } else {
0400                 if (s[i] != QLatin1Char('\n')) {
0401                     break;
0402                 }
0403             }
0404         }
0405         s.truncate(i + 1);
0406     } else {
0407         if (s.right(1) == QLatin1Char('\n')) {
0408             s.chop(1);   // remove trailing new-line
0409         }
0410     }
0411 
0412     return s;
0413 }
0414 
0415 void Content::fromUnicodeString(const QString &s)
0416 {
0417     QTextCodec *codec = QTextCodec::codecForName(contentType()->charset());
0418 
0419     if (!codec) {   // no suitable codec found => try local settings and hope the best ;-)
0420         codec = QTextCodec::codecForLocale();
0421         QByteArray chset = codec->name();
0422         contentType()->setCharset(chset);
0423     }
0424 
0425     d_ptr->body = codec->fromUnicode(s);
0426     contentTransferEncoding()->setDecoded(true);   //text is always decoded
0427 }
0428 
0429 Content *Content::textContent()
0430 {
0431     Content *ret = nullptr;
0432 
0433     //return the first content with mimetype=text/*
0434     if (contentType()->isText()) {
0435         ret = this;
0436     } else {
0437         const auto contents = d_ptr->contents();
0438         for (Content *c : contents) {
0439             if ((ret = c->textContent()) != nullptr) {
0440                 break;
0441             }
0442         }
0443     }
0444     return ret;
0445 }
0446 
0447 QVector<Content*> Content::attachments()
0448 {
0449     QVector<Content*> result;
0450 
0451     auto ct = contentType(false);
0452     if (ct && ct->isMultipart() &&
0453         !ct->isSubtype("related") /* && !ct->isSubtype("alternative")*/) {
0454       const QVector<Content *> contentsList = contents();
0455       result.reserve(contentsList.count());
0456       for (Content *child : contentsList) {
0457         if (isAttachment(child)) {
0458           result.push_back(child);
0459         } else {
0460           result += child->attachments();
0461         }
0462       }
0463     }
0464 
0465     return result;
0466 }
0467 
0468 QVector<Content*> Content::contents() const
0469 {
0470     return d_ptr->contents();
0471 }
0472 
0473 void Content::replaceContent(Content *oldContent, Content *newContent)
0474 {
0475     Q_D( Content );
0476     if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( oldContent ) ) {
0477       return;
0478     }
0479 
0480     d->multipartContents.removeAll( oldContent );
0481     delete oldContent;
0482     d->multipartContents.append( newContent );
0483     if( newContent->parent() != this ) {
0484       // If the content was part of something else, this will remove it from there.
0485       newContent->setParent( this );
0486     }
0487 }
0488 
0489 
0490 void Content::addContent(Content *c, bool prepend)
0491 {
0492     Q_D(Content);
0493 
0494     // This method makes no sense for encapsulated messages
0495     Q_ASSERT(!bodyIsMessage());
0496 
0497     // If this message is single-part; make it multipart first.
0498     if (d->multipartContents.isEmpty() && !contentType()->isMultipart()) {
0499         // The current body will be our first sub-Content.
0500         auto *main = new Content(this);
0501 
0502         // Move the MIME headers to the newly created sub-Content.
0503         // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers
0504         // are not moved to the subcontent; they remain with the top-level content.
0505         for (auto it = d->headers.begin(); it != d->headers.end();) {
0506             if ((*it)->isMimeHeader()) {
0507                 // Add to new content.
0508                 main->setHeader(*it);
0509                 // Remove from this content.
0510                 it = d->headers.erase(it);
0511             } else {
0512                 ++it;
0513             }
0514         }
0515 
0516         // Adjust the Content-Type of the newly created sub-Content.
0517         main->contentType()->setCategory(Headers::CCmixedPart);
0518 
0519         // Move the body to the new subcontent.
0520         main->setBody(d->body);
0521         d->body.clear();
0522 
0523         // Add the subcontent.
0524         d->multipartContents.append(main);
0525 
0526         // Convert this content to "multipart/mixed".
0527         Headers::ContentType *ct = contentType();
0528         ct->setMimeType("multipart/mixed");
0529         ct->setBoundary(multiPartBoundary());
0530         ct->setCategory(Headers::CCcontainer);
0531         auto cte = contentTransferEncoding();
0532         cte->setEncoding(Headers::CE7Bit);
0533         cte->setDecoded(true);
0534     }
0535 
0536     // Add the new content.
0537     if (prepend) {
0538         d->multipartContents.prepend(c);
0539     } else {
0540         d->multipartContents.append(c);
0541     }
0542 
0543     if (c->parent() != this) {
0544         // If the content was part of something else, this will remove it from there.
0545         c->setParent(this);
0546     }
0547 }
0548 
0549 void Content::removeContent(Content *c, bool del)
0550 {
0551     Q_D(Content);
0552     if (d->multipartContents.isEmpty() || !d->multipartContents.contains(c)) {
0553         return;
0554     }
0555 
0556     // This method makes no sense for encapsulated messages.
0557     // Should be covered by the above assert already, though.
0558     Q_ASSERT(!bodyIsMessage());
0559 
0560     d->multipartContents.removeAll(c);
0561     if (del) {
0562         delete c;
0563     } else {
0564         c->d_ptr->parent = nullptr;
0565     }
0566 
0567     // If only one content is left, turn this content into a single-part.
0568     if (d->multipartContents.count() == 1) {
0569         Content *main = d->multipartContents.constFirst();
0570 
0571         // Move all headers from the old subcontent to ourselves.
0572         // NOTE: This also sets the new Content-Type.
0573         const auto headers = main->d_ptr->headers;
0574         for (Headers::Base *h : headers) {
0575             setHeader(h);   // Will remove the old one if present.
0576         }
0577         main->d_ptr->headers.clear();
0578 
0579         // Move the body.
0580         d->body = main->body();
0581 
0582         // Delete the old subcontent.
0583         delete main;
0584         d->multipartContents.clear();
0585     }
0586 }
0587 
0588 void Content::changeEncoding(Headers::contentEncoding e)
0589 {
0590     // This method makes no sense for encapsulated messages, they are always 7bit
0591     // encoded.
0592     Q_ASSERT(!bodyIsMessage());
0593 
0594     Headers::ContentTransferEncoding *enc = contentTransferEncoding();
0595     if (enc->encoding() == e) {
0596         // Nothing to do.
0597         return;
0598     }
0599 
0600     if (d_ptr->decodeText(this)) {
0601         // This is textual content.  Textual content is stored decoded.
0602         Q_ASSERT(enc->isDecoded());
0603         enc->setEncoding(e);
0604     } else {
0605         // This is non-textual content.  Re-encode it.
0606         if (e == Headers::CEbase64) {
0607             KCodecs::base64Encode(decodedContent(), d_ptr->body, true);
0608             enc->setEncoding(e);
0609             enc->setDecoded(false);
0610         } else {
0611             // It only makes sense to convert binary stuff to base64.
0612             Q_ASSERT(false);
0613         }
0614     }
0615 }
0616 
0617 QVector<Headers::Base*> Content::headers() const
0618 {
0619     return d_ptr->headers;
0620 }
0621 
0622 Headers::Base *Content::headerByType(const char *type) const
0623 {
0624     Q_ASSERT(type  && *type);
0625 
0626     for (Headers::Base *h : std::as_const(d_ptr->headers)) {
0627         if (h->is(type)) {
0628             return h; // Found.
0629         }
0630     }
0631 
0632     return nullptr; // Not found.
0633 }
0634 
0635 QVector<Headers::Base*> Content::headersByType(const char *type) const
0636 {
0637     Q_ASSERT(type && *type);
0638 
0639     QVector<Headers::Base*> result;
0640 
0641     for (Headers::Base *h : std::as_const(d_ptr->headers)) {
0642         if (h->is(type)) {
0643             result << h;
0644         }
0645     }
0646 
0647     return result;
0648 }
0649 
0650 void Content::setHeader(Headers::Base *h)
0651 {
0652     Q_ASSERT(h);
0653     removeHeader(h->type());
0654     appendHeader(h);
0655 }
0656 
0657 void Content::appendHeader(Headers::Base *h)
0658 {
0659     Q_D(Content);
0660     d->headers.append(h);
0661 }
0662 
0663 bool Content::removeHeader(const char *type)
0664 {
0665     Q_D(Content);
0666     const auto endIt = d->headers.end();
0667     for (auto it = d->headers.begin(); it != endIt; ++it) {
0668         if ((*it)->is(type)) {
0669             delete(*it);
0670             d->headers.erase(it);
0671             return true;
0672         }
0673     }
0674 
0675     return false;
0676 }
0677 
0678 bool Content::hasHeader(const char* type) const
0679 {
0680     return headerByType(type) != nullptr;
0681 }
0682 
0683 int Content::size()
0684 {
0685     int ret = d_ptr->body.length();
0686 
0687     if (contentTransferEncoding()->encoding() == Headers::CEbase64) {
0688         KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64");
0689         return codec->maxEncodedSizeFor(ret);
0690     }
0691 
0692     // Not handling quoted-printable here since that requires actually
0693     // converting the content, and that is O(size_of_content).
0694     // For quoted-printable, this is only an approximate size.
0695 
0696     return ret;
0697 }
0698 
0699 int Content::storageSize() const
0700 {
0701     const Q_D(Content);
0702     int s = d->head.size();
0703 
0704     if (d->contents().isEmpty()) {
0705         s += d->body.size();
0706     } else {
0707 
0708         // FIXME: This should take into account the boundary headers that are added in
0709         //        encodedContent!
0710         const auto contents = d->contents();
0711         for (Content *c : contents) {
0712             s += c->storageSize();
0713         }
0714     }
0715 
0716     return s;
0717 }
0718 
0719 int Content::lineCount() const
0720 {
0721     const Q_D(Content);
0722     int ret = 0;
0723     if (!isTopLevel()) {
0724         ret += d->head.count('\n');
0725     }
0726     ret += d->body.count('\n');
0727 
0728     const auto contents = d->contents();
0729     for (Content *c : contents) {
0730         ret += c->lineCount();
0731     }
0732 
0733     return ret;
0734 }
0735 
0736 bool ContentPrivate::decodeText(Content *q)
0737 {
0738     Headers::ContentTransferEncoding *enc = q->contentTransferEncoding();
0739 
0740     if (!q->contentType()->isText()) {
0741         return false; //non textual data cannot be decoded here => use decodedContent() instead
0742     }
0743     if (enc->isDecoded()) {
0744         return true; //nothing to do
0745     }
0746 
0747     switch (enc->encoding()) {
0748     case Headers::CEbase64 :
0749         body = KCodecs::base64Decode(body);
0750         break;
0751     case Headers::CEquPr :
0752         body = KCodecs::quotedPrintableDecode(body);
0753         break;
0754     case Headers::CEuuenc :
0755         body = KCodecs::uudecode(body);
0756         break;
0757     case Headers::CEbinary :
0758         // nothing to decode
0759     default :
0760         break;
0761     }
0762     if (!body.endsWith("\n")) {
0763         body.append("\n");
0764     }
0765     enc->setDecoded(true);
0766     return true;
0767 }
0768 
0769 QByteArray Content::defaultCharset()
0770 {
0771     return KMime::cachedCharset(QByteArrayLiteral("ISO-8859-1"));
0772 }
0773 
0774 Content *KMime::Content::content(const ContentIndex &index) const
0775 {
0776     if (!index.isValid()) {
0777         return const_cast<KMime::Content *>(this);
0778     }
0779     ContentIndex idx = index;
0780     unsigned int i = idx.pop() - 1; // one-based -> zero-based index
0781     if (i < static_cast<unsigned int>(d_ptr->contents().size())) {
0782         return d_ptr->contents().at(i)->content(idx);
0783     } else {
0784         return nullptr;
0785     }
0786 }
0787 
0788 ContentIndex KMime::Content::indexForContent(Content *content) const
0789 {
0790     int i = d_ptr->contents().indexOf(content);
0791     if (i >= 0) {
0792         ContentIndex ci;
0793         ci.push(i + 1);   // zero-based -> one-based index
0794         return ci;
0795     }
0796     // not found, we need to search recursively
0797     for (int i = 0; i < d_ptr->contents().size(); ++i) {
0798         ContentIndex ci = d_ptr->contents().at(i)->indexForContent(content);
0799         if (ci.isValid()) {
0800             // found it
0801             ci.push(i + 1);   // zero-based -> one-based index
0802             return ci;
0803         }
0804     }
0805     return {}; // not found
0806 }
0807 
0808 bool Content::isTopLevel() const
0809 {
0810     return d_ptr->parent == nullptr;
0811 }
0812 
0813 void Content::setParent(Content *parent)
0814 {
0815     // Make sure the Content is only in the contents list of one parent object
0816     Content *oldParent = d_ptr->parent;
0817     if (oldParent) {
0818         if (!oldParent->contents().isEmpty() && oldParent->contents().contains(this)) {
0819             oldParent->removeContent(this);
0820         }
0821     }
0822 
0823     d_ptr->parent = parent;
0824     if (parent) {
0825         if (!parent->contents().isEmpty() && !parent->contents().contains(this)) {
0826             parent->addContent(this);
0827         }
0828     }
0829 }
0830 
0831 Content *Content::parent() const
0832 {
0833     return d_ptr->parent;
0834 }
0835 
0836 Content *Content::topLevel() const
0837 {
0838     auto *top = const_cast<Content *>(this);
0839     Content *c = parent();
0840     while (c) {
0841         top = c;
0842         c = c->parent();
0843     }
0844 
0845     return top;
0846 }
0847 
0848 ContentIndex Content::index() const
0849 {
0850     Content *top = topLevel();
0851     if (top) {
0852         return top->indexForContent(const_cast<Content *>(this));
0853     }
0854 
0855     return indexForContent(const_cast<Content *>(this));
0856 }
0857 
0858 Message::Ptr Content::bodyAsMessage() const
0859 {
0860     if (bodyIsMessage() && d_ptr->bodyAsMessage) {
0861         return d_ptr->bodyAsMessage;
0862     } else {
0863       return {};
0864     }
0865 }
0866 
0867 bool Content::bodyIsMessage() const
0868 {
0869     // Use const_case here to work around API issue that neither header() nor hasHeader() are
0870     // const, even though they should be
0871     return const_cast<Content *>(this)->header<Headers::ContentType>(false) &&
0872            const_cast<Content *>(this)->header<Headers::ContentType>(true)
0873            ->mimeType().toLower() == "message/rfc822";
0874 }
0875 
0876 // @cond PRIVATE
0877 #define kmime_mk_header_accessor( type, method ) \
0878     Headers::type *Content::method( bool create ) { \
0879         return header<Headers::type>( create ); \
0880     }
0881 
0882 kmime_mk_header_accessor(ContentType, contentType)
0883 kmime_mk_header_accessor(ContentTransferEncoding, contentTransferEncoding)
0884 kmime_mk_header_accessor(ContentDisposition, contentDisposition)
0885 kmime_mk_header_accessor(ContentDescription, contentDescription)
0886 kmime_mk_header_accessor(ContentLocation, contentLocation)
0887 kmime_mk_header_accessor(ContentID, contentID)
0888 
0889 #undef kmime_mk_header_accessor
0890 // @endcond
0891 
0892 void ContentPrivate::clearBodyMessage()
0893 {
0894     bodyAsMessage.reset();
0895 }
0896 
0897 QVector<Content*> ContentPrivate::contents() const
0898 {
0899     Q_ASSERT(multipartContents.isEmpty() || !bodyAsMessage);
0900     if (bodyAsMessage) {
0901         return QVector<Content*>() << bodyAsMessage.data();
0902     } else {
0903         return multipartContents;
0904     }
0905 }
0906 
0907 bool ContentPrivate::parseUuencoded(Content *q)
0908 {
0909     Parser::UUEncoded uup(body, KMime::extractHeader(head, "Subject"));
0910     if (!uup.parse()) {
0911         return false; // Parsing failed.
0912     }
0913 
0914     Headers::ContentType *ct = q->contentType();
0915     ct->clear();
0916 
0917     if (uup.isPartial()) {
0918         // This seems to be only a part of the message, so we treat it as "message/partial".
0919         ct->setMimeType("message/partial");
0920         //ct->setId( uniqueString() ); not needed yet
0921         ct->setPartialParams(uup.partialCount(), uup.partialNumber());
0922         q->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
0923     } else {
0924         // This is a complete message, so treat it as "multipart/mixed".
0925         const auto prevBody = body;
0926         body.clear();
0927         ct->setMimeType("multipart/mixed");
0928         ct->setBoundary(multiPartBoundary());
0929         ct->setCategory(Headers::CCcontainer);
0930         auto cte = q->contentTransferEncoding();
0931         cte->setEncoding(Headers::CE7Bit);
0932         cte->setDecoded(true);
0933 
0934         // Add the plain text part first.
0935         Q_ASSERT(multipartContents.isEmpty());
0936         {
0937             auto *c = new Content(q);
0938             c->contentType()->setMimeType("text/plain");
0939             c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
0940             c->setBody(uup.textPart());
0941             multipartContents.append(c);
0942         }
0943 
0944         // Now add each of the binary parts as sub-Contents.
0945         for (int i = 0; i < uup.binaryParts().count(); ++i) {
0946             auto *c = new Content(q);
0947             c->contentType()->setMimeType(uup.mimeTypes().at(i));
0948             c->contentType()->setName(QLatin1String(uup.filenames().at(i)), QByteArray(/*charset*/));
0949             c->contentTransferEncoding()->setEncoding(Headers::CEuuenc);
0950             c->contentTransferEncoding()->setDecoded(false);
0951             c->contentDisposition()->setDisposition(Headers::CDattachment);
0952             c->contentDisposition()->setFilename(QLatin1String(uup.filenames().at(i)));
0953             // uup.binaryParts().at(i) does no longer have the uuencode header, which makes KCodecs fail since 5c66308c4786ef7fbf77b0e306e73f7d4ac3431b
0954             c->setBody(prevBody);
0955             c->changeEncoding(Headers::CEbase64);   // Convert to base64.
0956             multipartContents.append(c);
0957         }
0958     }
0959 
0960     return true; // Parsing successful.
0961 }
0962 
0963 bool ContentPrivate::parseYenc(Content *q)
0964 {
0965     Parser::YENCEncoded yenc(body);
0966     if (!yenc.parse()) {
0967         return false; // Parsing failed.
0968     }
0969 
0970     Headers::ContentType *ct = q->contentType();
0971     ct->clear();
0972 
0973     if (yenc.isPartial()) {
0974         // Assume there is exactly one decoded part.  Treat this as "message/partial".
0975         ct->setMimeType("message/partial");
0976         //ct->setId( uniqueString() ); not needed yet
0977         ct->setPartialParams(yenc.partialCount(), yenc.partialNumber());
0978         q->contentTransferEncoding()->setEncoding(Headers::CEbinary);
0979         q->changeEncoding(Headers::CEbase64);   // Convert to base64.
0980     } else {
0981         // This is a complete message, so treat it as "multipart/mixed".
0982         body.clear();
0983         ct->setMimeType("multipart/mixed");
0984         ct->setBoundary(multiPartBoundary());
0985         ct->setCategory(Headers::CCcontainer);
0986         auto cte = q->contentTransferEncoding();
0987         cte->setEncoding(Headers::CE7Bit);
0988         cte->setDecoded(true);
0989 
0990         // Add the plain text part first.
0991         Q_ASSERT(multipartContents.isEmpty());
0992         {
0993             auto *c = new Content(q);
0994             c->contentType()->setMimeType("text/plain");
0995             c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
0996             c->setBody(yenc.textPart());
0997             multipartContents.append(c);
0998         }
0999 
1000         // Now add each of the binary parts as sub-Contents.
1001         for (int i = 0; i < yenc.binaryParts().count(); i++) {
1002             auto *c = new Content(q);
1003             c->contentType()->setMimeType(yenc.mimeTypes().at(i));
1004             c->contentType()->setName(QLatin1String(yenc.filenames().at(i)), QByteArray(/*charset*/));
1005             c->contentTransferEncoding()->setEncoding(Headers::CEbinary);
1006             c->contentDisposition()->setDisposition(Headers::CDattachment);
1007             c->contentDisposition()->setFilename(QLatin1String(yenc.filenames().at(i)));
1008             c->setBody(yenc.binaryParts().at(i));     // Yenc bodies are binary.
1009             c->changeEncoding(Headers::CEbase64);   // Convert to base64.
1010             multipartContents.append(c);
1011         }
1012     }
1013 
1014     return true; // Parsing successful.
1015 }
1016 
1017 bool ContentPrivate::parseMultipart(Content *q)
1018 {
1019     const Headers::ContentType *ct = q->contentType();
1020     const QByteArray boundary = ct->boundary();
1021     if (boundary.isEmpty()) {
1022         return false; // Parsing failed; invalid multipart content.
1023     }
1024     Parser::MultiPart mpp(body, boundary);
1025     if (!mpp.parse()) {
1026         return false; // Parsing failed.
1027     }
1028 
1029     preamble = mpp.preamble();
1030     epilogue = mpp.epilouge();
1031 
1032     // Determine the category of the subparts (used in attachments()).
1033     Headers::contentCategory cat;
1034     if (ct->isSubtype("alternative")) {
1035         cat = Headers::CCalternativePart;
1036     } else {
1037         cat = Headers::CCmixedPart; // Default to "mixed".
1038     }
1039 
1040     // Create a sub-Content for every part.
1041     Q_ASSERT(multipartContents.isEmpty());
1042     body.clear();
1043     const auto parts = mpp.parts();
1044     for (const QByteArray &part : parts) {
1045         auto *c = new Content(q);
1046         c->setContent(part);
1047         c->setFrozen(frozen);
1048         c->parse();
1049         c->contentType()->setCategory(cat);
1050         multipartContents.append(c);
1051     }
1052 
1053     return true; // Parsing successful.
1054 }
1055 
1056 } // namespace KMime