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