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