diff --git a/src/kmime_content.cpp b/src/kmime_content.cpp index 629d490..42a8afe 100644 --- a/src/kmime_content.cpp +++ b/src/kmime_content.cpp @@ -1,1051 +1,1051 @@ /* kmime_content.cpp KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the Content class. @brief Defines the Content class. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #include "kmime_content.h" #include "kmime_content_p.h" #include "kmime_message.h" #include "kmime_header_parsing.h" #include "kmime_header_parsing_p.h" #include "kmime_parsers.h" #include "kmime_util_p.h" #include #include #include #include #include using namespace KMime; namespace KMime { Content::Content(Content *parent) : d_ptr(new ContentPrivate) { d_ptr->parent = parent; } Content::~Content() { Q_D(Content); qDeleteAll(d->headers); d->headers.clear(); delete d_ptr; d_ptr = nullptr; } bool Content::hasContent() const { return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents().isEmpty(); } void Content::setContent(const QByteArray &s) { Q_D(Content); KMime::HeaderParsing::extractHeaderAndBody(s, d->head, d->body); } QByteArray Content::head() const { return d_ptr->head; } void Content::setHead(const QByteArray &head) { d_ptr->head = head; if (!head.endsWith('\n')) { d_ptr->head += '\n'; } } QByteArray Content::body() const { return d_ptr->body; } void Content::setBody(const QByteArray &body) { d_ptr->body = body; } QByteArray Content::preamble() const { return d_ptr->preamble; } void Content::setPreamble(const QByteArray &preamble) { d_ptr->preamble = preamble; } QByteArray Content::epilogue() const { return d_ptr->epilogue; } void Content::setEpilogue(const QByteArray &epilogue) { d_ptr->epilogue = epilogue; } void Content::parse() { Q_D(Content); // Clean up old headers and parse them again. qDeleteAll(d->headers); d->headers.clear(); d->headers = HeaderParsing::parseHeaders(d->head); // If we are frozen, save the body as-is. This is done because parsing // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.) if (d->frozen) { d->frozenBody = d->body; } // Clean up old sub-Contents and parse them again. qDeleteAll(d->multipartContents); d->multipartContents.clear(); d->clearBodyMessage(); Headers::ContentType *ct = contentType(); if (ct->isText()) { // This content is either text, or of unknown type. if (d->parseUuencoded(this)) { // This is actually uuencoded content generated by broken software. } else if (d->parseYenc(this)) { // This is actually yenc content generated by broken software. } else { // This is just plain text. } } else if (ct->isMultipart()) { // This content claims to be MIME multipart. if (d->parseMultipart(this)) { // This is actual MIME multipart content. } else { // Parsing failed; treat this content as "text/plain". ct->setMimeType("text/plain"); ct->setCharset("US-ASCII"); } } else { // This content is something else, like an encapsulated message or a binary attachment // or something like that if (bodyIsMessage()) { d->bodyAsMessage = Message::Ptr(new Message); d->bodyAsMessage->setContent(d->body); d->bodyAsMessage->setFrozen(d->frozen); d->bodyAsMessage->parse(); d->bodyAsMessage->d_ptr->parent = this; // Clear the body, as it is now represented by d->bodyAsMessage. This is the same behavior // as with multipart contents, since parseMultipart() clears the body as well d->body.clear(); } } } bool Content::isFrozen() const { return d_ptr->frozen; } void Content::setFrozen(bool frozen) { d_ptr->frozen = frozen; } void Content::assemble() { Q_D(Content); if (d->frozen) { return; } d->head = assembleHeaders(); foreach (Content *c, contents()) { c->assemble(); } } QByteArray Content::assembleHeaders() { Q_D(Content); QByteArray newHead; for (const Headers::Base *h : qAsConst(d->headers)) { if (!h->isEmpty()) { newHead += h->as7BitString() + '\n'; } } return newHead; } void Content::clear() { Q_D(Content); qDeleteAll(d->headers); d->headers.clear(); clearContents(); d->head.clear(); d->body.clear(); } void Content::clearContents(bool del) { Q_D(Content); if (del) { qDeleteAll(d->multipartContents); } d->multipartContents.clear(); d->clearBodyMessage(); } QByteArray Content::encodedContent(bool useCrLf) { QByteArray encodedContentData = head(); // return value; initialise with the head data const QByteArray encodedBodyData = encodedBody(); /* Make sure, that head and body have at least two newlines as seperator, otherwise add one. * If we have enough newlines as sperator, than we should not change the number of newlines * to not break digital signatures */ if (!encodedContentData.endsWith("\n\n") && !encodedBodyData.startsWith("\n\n") && !(encodedContentData.endsWith("\n") && encodedBodyData.startsWith("\n"))){ encodedContentData += '\n'; } encodedContentData += encodedBodyData; if (useCrLf) { return LFtoCRLF(encodedContentData); } else { return encodedContentData; } } QByteArray Content::encodedBody() { Q_D(Content); QByteArray e; // Body. if (d->frozen) { // This Content is frozen. if (d->frozenBody.isEmpty()) { // This Content has never been parsed. e += d->body; } else { // Use the body as it was before parsing. e += d->frozenBody; } } else if (bodyIsMessage() && d->bodyAsMessage) { // This is an encapsulated message // No encoding needed, as the ContentTransferEncoding can only be 7bit // for encapsulated messages e += d->bodyAsMessage->encodedContent(); } else if (!d->body.isEmpty()) { // This is a single-part Content. Headers::ContentTransferEncoding *enc = contentTransferEncoding(); if (enc->needToEncode()) { if (enc->encoding() == Headers::CEquPr) { e += KCodecs::quotedPrintableEncode(d->body, false); } else { QByteArray encoded; KCodecs::base64Encode(d->body, encoded, true); e += encoded; e += '\n'; } } else { e += d->body; } } if (!d->frozen && !d->multipartContents.isEmpty()) { // This is a multipart Content. Headers::ContentType *ct = contentType(); QByteArray boundary = "\n--" + ct->boundary(); if (!d->preamble.isEmpty()) { e += d->preamble; } //add all (encoded) contents separated by boundaries - foreach (Content *c, d->multipartContents) { + for (Content *c : qAsConst(d->multipartContents)) { e += boundary + '\n'; e += c->encodedContent(false); // don't convert LFs here, we do that later!!!!! } //finally append the closing boundary e += boundary + "--\n"; if (!d->epilogue.isEmpty()) { e += d->epilogue; } } return e; } QByteArray Content::decodedContent() { QByteArray ret; Headers::ContentTransferEncoding *ec = contentTransferEncoding(); bool removeTrailingNewline = false; if (d_ptr->body.isEmpty()) { return ret; } if (ec->isDecoded()) { ret = d_ptr->body; //Laurent Fix bug #311267 //removeTrailingNewline = true; } else { switch (ec->encoding()) { case Headers::CEbase64 : { KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64"); Q_ASSERT(codec); ret.resize(codec->maxDecodedSizeFor(d_ptr->body.size())); QScopedPointer decoder(codec->makeDecoder()); QByteArray::const_iterator inputIt = d_ptr->body.constBegin(); QByteArray::iterator resultIt = ret.begin(); decoder->decode(inputIt, d_ptr->body.constEnd(), resultIt, ret.end()); ret.truncate(resultIt - ret.begin()); break; } case Headers::CEquPr : ret = KCodecs::quotedPrintableDecode(d_ptr->body); removeTrailingNewline = true; break; case Headers::CEuuenc : KCodecs::uudecode(d_ptr->body, ret); break; case Headers::CEbinary : ret = d_ptr->body; removeTrailingNewline = false; break; default : ret = d_ptr->body; removeTrailingNewline = true; } } if (removeTrailingNewline && (ret.size() > 0) && (ret[ret.size() - 1] == '\n')) { ret.resize(ret.size() - 1); } return ret; } QString Content::decodedText(bool trimText, bool removeTrailingNewlines) { if (!d_ptr->decodeText(this)) { //this is not a text content !! return QString(); } bool ok = true; QTextCodec *codec = KCharsets::charsets()->codecForName(QLatin1String(contentType()->charset()), ok); if (!ok || codec == nullptr) { // no suitable codec found => try local settings and hope the best ;-) codec = QTextCodec::codecForLocale(); QByteArray chset = codec->name(); contentType()->setCharset(chset); } QString s = codec->toUnicode(d_ptr->body.data(), d_ptr->body.length()); if (trimText || removeTrailingNewlines) { int i; for (i = s.length() - 1; i >= 0; --i) { if (trimText) { if (!s[i].isSpace()) { break; } } else { if (s[i] != QLatin1Char('\n')) { break; } } } s.truncate(i + 1); } else { if (s.right(1) == QLatin1Char('\n')) { s.chop(1); // remove trailing new-line } } return s; } void Content::fromUnicodeString(const QString &s) { bool ok = true; QTextCodec *codec = KCharsets::charsets()->codecForName(QLatin1String(contentType()->charset()), ok); if (!ok) { // no suitable codec found => try local settings and hope the best ;-) codec = QTextCodec::codecForLocale(); QByteArray chset = codec->name(); contentType()->setCharset(chset); } d_ptr->body = codec->fromUnicode(s); contentTransferEncoding()->setDecoded(true); //text is always decoded } Content *Content::textContent() { Content *ret = nullptr; //return the first content with mimetype=text/* if (contentType()->isText()) { ret = this; } else { foreach (Content *c, d_ptr->contents()) { if ((ret = c->textContent()) != nullptr) { break; } } } return ret; } QVector Content::attachments() { QVector result; auto ct = contentType(false); if (ct && ct->isMultipart() && !ct->isSubtype("related") && !ct->isSubtype("alternative")) { const QVector contentsList = contents(); result.reserve(contentsList.count()); - Q_FOREACH (Content *child, contentsList) { + for (Content *child : contentsList) { if (isAttachment(child)) result.push_back(child); else result += child->attachments(); } } return result; } QVector Content::contents() const { return d_ptr->contents(); } void Content::replaceContent(Content *oldContent, Content *newContent) { Q_D( Content ); if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( oldContent ) ) { return; } d->multipartContents.removeAll( oldContent ); delete oldContent; d->multipartContents.append( newContent ); if( newContent->parent() != this ) { // If the content was part of something else, this will remove it from there. newContent->setParent( this ); } } void Content::addContent(Content *c, bool prepend) { Q_D(Content); // This method makes no sense for encapsulated messages Q_ASSERT(!bodyIsMessage()); // If this message is single-part; make it multipart first. if (d->multipartContents.isEmpty() && !contentType()->isMultipart()) { // The current body will be our first sub-Content. Content *main = new Content(this); // Move the MIME headers to the newly created sub-Content. // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers // are not moved to the subcontent; they remain with the top-level content. for (auto it = d->headers.begin(); it != d->headers.end();) { if ((*it)->isMimeHeader()) { // Add to new content. main->setHeader(*it); // Remove from this content. it = d->headers.erase(it); } else { ++it; } } // Adjust the Content-Type of the newly created sub-Content. main->contentType()->setCategory(Headers::CCmixedPart); // Move the body to the new subcontent. main->setBody(d->body); d->body.clear(); // Add the subcontent. d->multipartContents.append(main); // Convert this content to "multipart/mixed". Headers::ContentType *ct = contentType(); ct->setMimeType("multipart/mixed"); ct->setBoundary(multiPartBoundary()); ct->setCategory(Headers::CCcontainer); auto cte = contentTransferEncoding(); cte->setEncoding(Headers::CE7Bit); cte->setDecoded(true); } // Add the new content. if (prepend) { d->multipartContents.prepend(c); } else { d->multipartContents.append(c); } if (c->parent() != this) { // If the content was part of something else, this will remove it from there. c->setParent(this); } } void Content::removeContent(Content *c, bool del) { Q_D(Content); if (d->multipartContents.isEmpty() || !d->multipartContents.contains(c)) { return; } // This method makes no sense for encapsulated messages. // Should be covered by the above assert already, though. Q_ASSERT(!bodyIsMessage()); d->multipartContents.removeAll(c); if (del) { delete c; } else { c->d_ptr->parent = nullptr; } // If only one content is left, turn this content into a single-part. if (d->multipartContents.count() == 1) { Content *main = d->multipartContents.first(); // Move all headers from the old subcontent to ourselves. // NOTE: This also sets the new Content-Type. foreach (Headers::Base *h, main->d_ptr->headers) { setHeader(h); // Will remove the old one if present. } main->d_ptr->headers.clear(); // Move the body. d->body = main->body(); // Delete the old subcontent. delete main; d->multipartContents.clear(); } } void Content::changeEncoding(Headers::contentEncoding e) { // This method makes no sense for encapsulated messages, they are always 7bit // encoded. Q_ASSERT(!bodyIsMessage()); Headers::ContentTransferEncoding *enc = contentTransferEncoding(); if (enc->encoding() == e) { // Nothing to do. return; } if (d_ptr->decodeText(this)) { // This is textual content. Textual content is stored decoded. Q_ASSERT(enc->isDecoded()); enc->setEncoding(e); } else { // This is non-textual content. Re-encode it. if (e == Headers::CEbase64) { KCodecs::base64Encode(decodedContent(), d_ptr->body, true); enc->setEncoding(e); enc->setDecoded(false); } else { // It only makes sense to convert binary stuff to base64. Q_ASSERT(false); } } } QVector Content::headers() const { return d_ptr->headers; } Headers::Base *Content::headerByType(const char *type) const { Q_ASSERT(type && *type); for (Headers::Base *h : qAsConst(d_ptr->headers)) { if (h->is(type)) { return h; // Found. } } return nullptr; // Not found. } QVector Content::headersByType(const char *type) const { Q_ASSERT(type && *type); QVector result; for (Headers::Base *h : qAsConst(d_ptr->headers)) { if (h->is(type)) { result << h; } } return result; } void Content::setHeader(Headers::Base *h) { Q_ASSERT(h); removeHeader(h->type()); appendHeader(h); } void Content::appendHeader(Headers::Base *h) { Q_D(Content); d->headers.append(h); } bool Content::removeHeader(const char *type) { Q_D(Content); const auto endIt = d->headers.end(); for (auto it = d->headers.begin(); it != endIt; ++it) if ((*it)->is(type)) { delete(*it); d->headers.erase(it); return true; } return false; } bool Content::hasHeader(const char* type) const { return headerByType(type) != nullptr; } int Content::size() { int ret = d_ptr->body.length(); if (contentTransferEncoding()->encoding() == Headers::CEbase64) { KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64"); return codec->maxEncodedSizeFor(ret); } // Not handling quoted-printable here since that requires actually // converting the content, and that is O(size_of_content). // For quoted-printable, this is only an approximate size. return ret; } int Content::storageSize() const { const Q_D(Content); int s = d->head.size(); if (d->contents().isEmpty()) { s += d->body.size(); } else { // FIXME: This should take into account the boundary headers that are added in // encodedContent! foreach (Content *c, d->contents()) { s += c->storageSize(); } } return s; } int Content::lineCount() const { const Q_D(Content); int ret = 0; if (!isTopLevel()) { ret += d->head.count('\n'); } ret += d->body.count('\n'); foreach (Content *c, d->contents()) { ret += c->lineCount(); } return ret; } bool ContentPrivate::decodeText(Content *q) { Headers::ContentTransferEncoding *enc = q->contentTransferEncoding(); if (!q->contentType()->isText()) { return false; //non textual data cannot be decoded here => use decodedContent() instead } if (enc->isDecoded()) { return true; //nothing to do } switch (enc->encoding()) { case Headers::CEbase64 : body = KCodecs::base64Decode(body); break; case Headers::CEquPr : body = KCodecs::quotedPrintableDecode(body); break; case Headers::CEuuenc : body = KCodecs::uudecode(body); break; case Headers::CEbinary : // nothing to decode default : break; } if (!body.endsWith("\n")) { body.append("\n"); } enc->setDecoded(true); return true; } QByteArray Content::defaultCharset() { return KMime::cachedCharset(QByteArrayLiteral("ISO-8859-1")); } Content *KMime::Content::content(const ContentIndex &index) const { if (!index.isValid()) { return const_cast(this); } ContentIndex idx = index; unsigned int i = idx.pop() - 1; // one-based -> zero-based index if (i < static_cast(d_ptr->contents().size())) { return d_ptr->contents().at(i)->content(idx); } else { return nullptr; } } ContentIndex KMime::Content::indexForContent(Content *content) const { int i = d_ptr->contents().indexOf(content); if (i >= 0) { ContentIndex ci; ci.push(i + 1); // zero-based -> one-based index return ci; } // not found, we need to search recursively for (int i = 0; i < d_ptr->contents().size(); ++i) { ContentIndex ci = d_ptr->contents().at(i)->indexForContent(content); if (ci.isValid()) { // found it ci.push(i + 1); // zero-based -> one-based index return ci; } } return ContentIndex(); // not found } bool Content::isTopLevel() const { return d_ptr->parent == nullptr; } void Content::setParent(Content *parent) { // Make sure the Content is only in the contents list of one parent object Content *oldParent = d_ptr->parent; if (oldParent) { if (!oldParent->contents().isEmpty() && oldParent->contents().contains(this)) { oldParent->removeContent(this); } } d_ptr->parent = parent; if (parent) { if (!parent->contents().isEmpty() && !parent->contents().contains(this)) { parent->addContent(this); } } } Content *Content::parent() const { return d_ptr->parent; } Content *Content::topLevel() const { Content *top = const_cast(this); Content *c = parent(); while (c) { top = c; c = c->parent(); } return top; } ContentIndex Content::index() const { Content *top = topLevel(); if (top) { return top->indexForContent(const_cast(this)); } return indexForContent(const_cast(this)); } Message::Ptr Content::bodyAsMessage() const { if (bodyIsMessage() && d_ptr->bodyAsMessage) { return d_ptr->bodyAsMessage; } else { return Message::Ptr(); } } bool Content::bodyIsMessage() const { // Use const_case here to work around API issue that neither header() nor hasHeader() are // const, even though they should be return const_cast(this)->header(false) && const_cast(this)->header(true) ->mimeType().toLower() == "message/rfc822"; } // @cond PRIVATE #define kmime_mk_header_accessor( type, method ) \ Headers::type *Content::method( bool create ) { \ return header( create ); \ } kmime_mk_header_accessor(ContentType, contentType) kmime_mk_header_accessor(ContentTransferEncoding, contentTransferEncoding) kmime_mk_header_accessor(ContentDisposition, contentDisposition) kmime_mk_header_accessor(ContentDescription, contentDescription) kmime_mk_header_accessor(ContentLocation, contentLocation) kmime_mk_header_accessor(ContentID, contentID) #undef kmime_mk_header_accessor // @endcond void ContentPrivate::clearBodyMessage() { bodyAsMessage.reset(); } QVector ContentPrivate::contents() const { Q_ASSERT(multipartContents.isEmpty() || !bodyAsMessage); if (bodyAsMessage) { return QVector() << bodyAsMessage.data(); } else { return multipartContents; } } bool ContentPrivate::parseUuencoded(Content *q) { Parser::UUEncoded uup(body, KMime::extractHeader(head, "Subject")); if (!uup.parse()) { return false; // Parsing failed. } Headers::ContentType *ct = q->contentType(); ct->clear(); if (uup.isPartial()) { // This seems to be only a part of the message, so we treat it as "message/partial". ct->setMimeType("message/partial"); //ct->setId( uniqueString() ); not needed yet ct->setPartialParams(uup.partialCount(), uup.partialNumber()); q->contentTransferEncoding()->setEncoding(Headers::CE7Bit); } else { // This is a complete message, so treat it as "multipart/mixed". const auto prevBody = body; body.clear(); ct->setMimeType("multipart/mixed"); ct->setBoundary(multiPartBoundary()); ct->setCategory(Headers::CCcontainer); auto cte = q->contentTransferEncoding(); cte->setEncoding(Headers::CE7Bit); cte->setDecoded(true); // Add the plain text part first. Q_ASSERT(multipartContents.isEmpty()); { Content *c = new Content(q); c->contentType()->setMimeType("text/plain"); c->contentTransferEncoding()->setEncoding(Headers::CE7Bit); c->setBody(uup.textPart()); multipartContents.append(c); } // Now add each of the binary parts as sub-Contents. for (int i = 0; i < uup.binaryParts().count(); ++i) { Content *c = new Content(q); c->contentType()->setMimeType(uup.mimeTypes().at(i)); c->contentType()->setName(QLatin1String(uup.filenames().at(i)), QByteArray(/*charset*/)); c->contentTransferEncoding()->setEncoding(Headers::CEuuenc); c->contentTransferEncoding()->setDecoded(false); c->contentDisposition()->setDisposition(Headers::CDattachment); c->contentDisposition()->setFilename(QLatin1String(uup.filenames().at(i))); // uup.binaryParts().at(i) does no longer have the uuencode header, which makes KCodecs fail since 5c66308c4786ef7fbf77b0e306e73f7d4ac3431b c->setBody(prevBody); c->changeEncoding(Headers::CEbase64); // Convert to base64. multipartContents.append(c); } } return true; // Parsing successful. } bool ContentPrivate::parseYenc(Content *q) { Parser::YENCEncoded yenc(body); if (!yenc.parse()) { return false; // Parsing failed. } Headers::ContentType *ct = q->contentType(); ct->clear(); if (yenc.isPartial()) { // Assume there is exactly one decoded part. Treat this as "message/partial". ct->setMimeType("message/partial"); //ct->setId( uniqueString() ); not needed yet ct->setPartialParams(yenc.partialCount(), yenc.partialNumber()); q->contentTransferEncoding()->setEncoding(Headers::CEbinary); q->changeEncoding(Headers::CEbase64); // Convert to base64. } else { // This is a complete message, so treat it as "multipart/mixed". body.clear(); ct->setMimeType("multipart/mixed"); ct->setBoundary(multiPartBoundary()); ct->setCategory(Headers::CCcontainer); auto cte = q->contentTransferEncoding(); cte->setEncoding(Headers::CE7Bit); cte->setDecoded(true); // Add the plain text part first. Q_ASSERT(multipartContents.isEmpty()); { Content *c = new Content(q); c->contentType()->setMimeType("text/plain"); c->contentTransferEncoding()->setEncoding(Headers::CE7Bit); c->setBody(yenc.textPart()); multipartContents.append(c); } // Now add each of the binary parts as sub-Contents. for (int i = 0; i < yenc.binaryParts().count(); i++) { Content *c = new Content(q); c->contentType()->setMimeType(yenc.mimeTypes().at(i)); c->contentType()->setName(QLatin1String(yenc.filenames().at(i)), QByteArray(/*charset*/)); c->contentTransferEncoding()->setEncoding(Headers::CEbinary); c->contentDisposition()->setDisposition(Headers::CDattachment); c->contentDisposition()->setFilename(QLatin1String(yenc.filenames().at(i))); c->setBody(yenc.binaryParts().at(i)); // Yenc bodies are binary. c->changeEncoding(Headers::CEbase64); // Convert to base64. multipartContents.append(c); } } return true; // Parsing successful. } bool ContentPrivate::parseMultipart(Content *q) { const Headers::ContentType *ct = q->contentType(); const QByteArray boundary = ct->boundary(); if (boundary.isEmpty()) { return false; // Parsing failed; invalid multipart content. } Parser::MultiPart mpp(body, boundary); if (!mpp.parse()) { return false; // Parsing failed. } preamble = mpp.preamble(); epilogue = mpp.epilouge(); // Determine the category of the subparts (used in attachments()). Headers::contentCategory cat; if (ct->isSubtype("alternative")) { cat = Headers::CCalternativePart; } else { cat = Headers::CCmixedPart; // Default to "mixed". } // Create a sub-Content for every part. Q_ASSERT(multipartContents.isEmpty()); body.clear(); const auto parts = mpp.parts(); - foreach (const QByteArray &part, parts) { + for (const QByteArray &part : parts) { Content *c = new Content(q); c->setContent(part); c->setFrozen(frozen); c->parse(); c->contentType()->setCategory(cat); multipartContents.append(c); } return true; // Parsing successful. } } // namespace KMime diff --git a/src/kmime_headers.cpp b/src/kmime_headers.cpp index 391b3ff..52a334b 100644 --- a/src/kmime_headers.cpp +++ b/src/kmime_headers.cpp @@ -1,2210 +1,2210 @@ /* -*- c++ -*- kmime_headers.cpp KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the various header classes: - header's base class defining the common interface - generic base classes for different types of fields - incompatible, Structured-based field classes - compatible, Unstructured-based field classes @brief Defines the various headers classes. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #include "kmime_headers.h" #include "kmime_headers_p.h" #include "kmime_util.h" #include "kmime_util_p.h" #include "kmime_codecs.h" #include "kmime_content.h" #include "kmime_header_parsing.h" #include "kmime_headerfactory_p.h" #include "kmime_debug.h" #include "kmime_warning.h" #include #include #include #include #include #include #include // macro to generate a default constructor implementation #define kmime_mk_trivial_ctor( subclass, baseclass ) \ subclass::subclass() : baseclass() \ { \ } \ \ subclass::~subclass() {} // end kmime_mk_trivial_ctor #define kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ subclass::subclass() : baseclass( new subclass##Private ) \ { \ } \ \ subclass::~subclass() { \ Q_D(subclass); \ delete d; \ d_ptr = nullptr; \ } // end kmime_mk_trivial_ctor_with_dptr #define kmime_mk_trivial_ctor_with_name( subclass, baseclass, name ) \ kmime_mk_trivial_ctor( subclass, baseclass ) \ \ const char *subclass::type() const \ { \ return staticType(); \ } \ const char *subclass::staticType() { return #name; } #define kmime_mk_trivial_ctor_with_name_and_dptr( subclass, baseclass, name ) \ kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ const char *subclass::type() const { return staticType(); } \ const char *subclass::staticType() { return #name; } #define kmime_mk_dptr_ctor( subclass, baseclass ) \ subclass::subclass( subclass##Private *d ) : baseclass( d ) {} using namespace KMime; using namespace KMime::Headers; using namespace KMime::Types; using namespace KMime::HeaderParsing; namespace KMime { namespace Headers { //--------------------------------------- Base::Base() : d_ptr(new BasePrivate) { } Base::Base(BasePrivate *dd) : d_ptr(dd) { } Base::~Base() { delete d_ptr; d_ptr = nullptr; } void Base::from7BitString(const char *s, size_t len) { from7BitString(QByteArray::fromRawData(s, len)); } QByteArray Base::rfc2047Charset() const { if (d_ptr->encCS.isEmpty()) { return Content::defaultCharset(); } else { return d_ptr->encCS; } } void Base::setRFC2047Charset(const QByteArray &cs) { d_ptr->encCS = cachedCharset(cs); } const char *Base::type() const { return ""; } bool Base::is(const char *t) const { return qstricmp(t, type()) == 0; } bool Base::isMimeHeader() const { return qstrnicmp(type(), "Content-", 8) == 0; } QByteArray Base::typeIntro() const { return QByteArray(type()) + ": "; } //-------------------------------------- namespace Generics { //------------------------------ //@cond PRIVATE kmime_mk_dptr_ctor(Unstructured, Base) //@endcond Unstructured::Unstructured() : Base(new UnstructuredPrivate) { } Unstructured::~Unstructured() { Q_D(Unstructured); delete d; d_ptr = nullptr; } void Unstructured::from7BitString(const QByteArray &s) { Q_D(Unstructured); d->decoded = KCodecs::decodeRFC2047String(s, &d->encCS, Content::defaultCharset()); } QByteArray Unstructured::as7BitString(bool withHeaderType) const { const Q_D(Unstructured); QByteArray result; if (withHeaderType) { result = typeIntro(); } result += encodeRFC2047String(d->decoded, d->encCS) ; return result; } void Unstructured::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(Unstructured); d->decoded = s; d->encCS = cachedCharset(b); } QString Unstructured::asUnicodeString() const { return d_func()->decoded; } void Unstructured::clear() { Q_D(Unstructured); d->decoded.truncate(0); } bool Unstructured::isEmpty() const { return d_func()->decoded.isEmpty(); } //------------------------------ //------------------------------ Structured::Structured() : Base(new StructuredPrivate) { } kmime_mk_dptr_ctor(Structured, Base) Structured::~Structured() { Q_D(Structured); delete d; d_ptr = nullptr; } void Structured::from7BitString(const char *s, size_t len) { Q_D(Structured); if (d->encCS.isEmpty()) { d->encCS = Content::defaultCharset(); } parse(s, s + len); } void Structured::from7BitString(const QByteArray &s) { #if 1 Q_D(Structured); //Bug about mailto with space which are replaced by "_" so it failed to parse //=> we reconvert to correct encoding as RFC2047 const QString str = KCodecs::decodeRFC2047String(s, &d->encCS, Content::defaultCharset()); const QByteArray ba = KCodecs::encodeRFC2047String(str, d->encCS); from7BitString(ba.constData(), ba.length()); #else from7BitString(s.constData(), s.length()); #endif } QString Structured::asUnicodeString() const { return QString::fromLatin1(as7BitString(false)); } void Structured::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(Structured); d->encCS = cachedCharset(b); from7BitString(s.toLatin1()); } //------------------------------ //-----
------------------------- Address::Address() : Structured(new AddressPrivate) { } kmime_mk_dptr_ctor(Address, Structured) Address:: ~Address() { } // helper method used in AddressList and MailboxList static bool stringToMailbox(const QByteArray &address, const QString &displayName, Types::Mailbox &mbox) { Types::AddrSpec addrSpec; mbox.setName(displayName); const char *cursor = address.constData(); if (!parseAngleAddr(cursor, cursor + address.length(), addrSpec)) { if (!parseAddrSpec(cursor, cursor + address.length(), addrSpec)) { qCWarning(KMIME_LOG) << "stringToMailbox: Invalid address"; return false; } } mbox.setAddress(addrSpec); return true; } //-----
------------------------- //------------------------------ kmime_mk_trivial_ctor_with_dptr(MailboxList, Address) kmime_mk_dptr_ctor(MailboxList, Address) QByteArray MailboxList::as7BitString(bool withHeaderType) const { const Q_D(MailboxList); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } - foreach (const Types::Mailbox &mbox, d->mailboxList) { + for (const Types::Mailbox &mbox : qAsConst(d->mailboxList)) { rv += mbox.as7BitString(d->encCS); rv += ", "; } rv.resize(rv.length() - 2); return rv; } void MailboxList::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(MailboxList); d->encCS = cachedCharset(b); from7BitString(encodeRFC2047Sentence(s, b)); } QString MailboxList::asUnicodeString() const { Q_D(const MailboxList); return Mailbox::listToUnicodeString(d->mailboxList); } void MailboxList::clear() { Q_D(MailboxList); d->mailboxList.clear(); } bool MailboxList::isEmpty() const { return d_func()->mailboxList.isEmpty(); } void MailboxList::addAddress(const Types::Mailbox &mbox) { Q_D(MailboxList); d->mailboxList.append(mbox); } void MailboxList::addAddress(const QByteArray &address, const QString &displayName) { Q_D(MailboxList); Types::Mailbox mbox; if (stringToMailbox(address, displayName, mbox)) { d->mailboxList.append(mbox); } } QVector MailboxList::addresses() const { QVector rv; rv.reserve(d_func()->mailboxList.count()); foreach (const Types::Mailbox &mbox, d_func()->mailboxList) { rv.append(mbox.address()); } return rv; } QStringList MailboxList::displayNames() const { Q_D(const MailboxList); QStringList rv; rv.reserve(d->mailboxList.count()); - foreach (const Types::Mailbox &mbox, d->mailboxList) { + for (const Types::Mailbox &mbox : qAsConst(d->mailboxList)) { if (mbox.hasName()) rv.append(mbox.name()); else rv.append(QString::fromLatin1(mbox.address())); } return rv; } QString MailboxList::displayString() const { Q_D(const MailboxList); if (d->mailboxList.size() == 1) { // fast-path to avoid temporary QStringList in the common case of just one From address const auto& mbox = d->mailboxList.at(0); if (mbox.hasName()) return mbox.name(); else return QString::fromLatin1(mbox.address()); } return displayNames().join(QLatin1String(", ")); } Types::Mailbox::List MailboxList::mailboxes() const { return d_func()->mailboxList; } bool MailboxList::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(MailboxList); // examples: // from := "From:" mailbox-list CRLF // sender := "Sender:" mailbox CRLF // parse an address-list: QVector maybeAddressList; if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) { return false; } d->mailboxList.clear(); d->mailboxList.reserve(maybeAddressList.count()); // extract the mailboxes and complain if there are groups: for (const auto &it : qAsConst(maybeAddressList)) { if (!(it).displayName.isEmpty()) { KMIME_WARN << "mailbox groups in header disallowing them! Name: \"" << (it).displayName << "\"" #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) << endl #else << Qt::endl #endif ; } d->mailboxList += (it).mailboxList; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(SingleMailbox, MailboxList) //@endcond bool SingleMailbox::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(MailboxList); if (!MailboxList::parse(scursor, send, isCRLF)) { return false; } if (d->mailboxList.count() > 1) { KMIME_WARN << "multiple mailboxes in header allowing only a single one!" #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) << endl #else << Qt::endl #endif ; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(AddressList, Address) kmime_mk_dptr_ctor(AddressList, Address) //@endcond QByteArray AddressList::as7BitString(bool withHeaderType) const { const Q_D(AddressList); if (d->addressList.isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } for (const Types::Address &addr : qAsConst(d->addressList)) { const auto mailBoxList = addr.mailboxList; for (const Types::Mailbox &mbox : mailBoxList) { rv += mbox.as7BitString(d->encCS); rv += ", "; } } rv.resize(rv.length() - 2); return rv; } void AddressList::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(AddressList); d->encCS = cachedCharset(b); from7BitString(encodeRFC2047Sentence(s, b)); } QString AddressList::asUnicodeString() const { Q_D(const AddressList); QStringList rv; - foreach (const Types::Address &addr, d->addressList) { + for (const Types::Address &addr : qAsConst(d->addressList)) { rv.reserve(rv.size() + addr.mailboxList.size()); foreach (const Types::Mailbox &mbox, addr.mailboxList) { rv.append(mbox.prettyAddress()); } } return rv.join(QLatin1String(", ")); } void AddressList::clear() { Q_D(AddressList); d->addressList.clear(); } bool AddressList::isEmpty() const { return d_func()->addressList.isEmpty(); } void AddressList::addAddress(const Types::Mailbox &mbox) { Q_D(AddressList); Types::Address addr; addr.mailboxList.append(mbox); d->addressList.append(addr); } void AddressList::addAddress(const QByteArray &address, const QString &displayName) { Q_D(AddressList); Types::Address addr; Types::Mailbox mbox; if (stringToMailbox(address, displayName, mbox)) { addr.mailboxList.append(mbox); d->addressList.append(addr); } } QVector AddressList::addresses() const { QVector rv; foreach (const Types::Address &addr, d_func()->addressList) { foreach (const Types::Mailbox &mbox, addr.mailboxList) { rv.append(mbox.address()); } } return rv; } QStringList AddressList::displayNames() const { Q_D(const AddressList); QStringList rv; for (const Types::Address &addr : qAsConst(d->addressList)) { const auto mailboxList = addr.mailboxList; for (const Types::Mailbox &mbox : mailboxList) { if (mbox.hasName()) rv.append(mbox.name()); else rv.append(QString::fromLatin1(mbox.address())); } } return rv; } QString AddressList::displayString() const { // optimize for single entry and avoid creation of the QStringList in that case? return displayNames().join(QLatin1String(", ")); } Types::Mailbox::List AddressList::mailboxes() const { Types::Mailbox::List rv; foreach (const Types::Address &addr, d_func()->addressList) { foreach (const Types::Mailbox &mbox, addr.mailboxList) { rv.append(mbox); } } return rv; } bool AddressList::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(AddressList); QVector maybeAddressList; if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) { return false; } d->addressList = maybeAddressList; return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(Token, Structured) kmime_mk_dptr_ctor(Token, Structured) //@endcond QByteArray Token::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } if (withHeaderType) { return typeIntro() + d_func()->token; } return d_func()->token; } void Token::clear() { Q_D(Token); d->token.clear(); } bool Token::isEmpty() const { return d_func()->token.isEmpty(); } QByteArray Token::token() const { return d_func()->token; } void Token::setToken(const QByteArray &t) { Q_D(Token); d->token = t; } bool Token::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Token); clear(); eatCFWS(scursor, send, isCRLF); // must not be empty: if (scursor == send) { return false; } QPair maybeToken; if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) { return false; } d->token = QByteArray(maybeToken.first, maybeToken.second); // complain if trailing garbage is found: eatCFWS(scursor, send, isCRLF); if (scursor != send) { KMIME_WARN << "trailing garbage after token in header allowing " "only a single token!" #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) << endl #else << Qt::endl #endif ; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(PhraseList, Structured) //@endcond QByteArray PhraseList::as7BitString(bool withHeaderType) const { const Q_D(PhraseList); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } for (int i = 0; i < d->phraseList.count(); ++i) { // FIXME: only encode when needed, quote when needed, etc. rv += encodeRFC2047String(d->phraseList[i], d->encCS, false, false); if (i != d->phraseList.count() - 1) { rv += ", "; } } return rv; } QString PhraseList::asUnicodeString() const { return d_func()->phraseList.join(QLatin1String(", ")); } void PhraseList::clear() { Q_D(PhraseList); d->phraseList.clear(); } bool PhraseList::isEmpty() const { return d_func()->phraseList.isEmpty(); } QStringList PhraseList::phrases() const { return d_func()->phraseList; } bool PhraseList::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(PhraseList); d->phraseList.clear(); while (scursor != send) { eatCFWS(scursor, send, isCRLF); // empty entry ending the list: OK. if (scursor == send) { return true; } // empty entry: ignore. if (*scursor == ',') { scursor++; continue; } QString maybePhrase; if (!parsePhrase(scursor, send, maybePhrase, isCRLF)) { return false; } d->phraseList.append(maybePhrase); eatCFWS(scursor, send, isCRLF); // non-empty entry ending the list: OK. if (scursor == send) { return true; } // comma separating the phrases: eat. if (*scursor == ',') { scursor++; } } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(DotAtom, Structured) //@endcond QByteArray DotAtom::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += d_func()->dotAtom.toLatin1(); // FIXME: encoding? return rv; } QString DotAtom::asUnicodeString() const { return d_func()->dotAtom; } void DotAtom::clear() { Q_D(DotAtom); d->dotAtom.clear(); } bool DotAtom::isEmpty() const { return d_func()->dotAtom.isEmpty(); } bool DotAtom::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(DotAtom); QString maybeDotAtom; if (!parseDotAtom(scursor, send, maybeDotAtom, isCRLF)) { return false; } d->dotAtom = maybeDotAtom; eatCFWS(scursor, send, isCRLF); if (scursor != send) { KMIME_WARN << "trailing garbage after dot-atom in header allowing " "only a single dot-atom!" #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) << endl #else << Qt::endl #endif ; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(Parametrized, Structured) kmime_mk_dptr_ctor(Parametrized, Structured) //@endcond QByteArray Parametrized::as7BitString(bool withHeaderType) const { const Q_D(Parametrized); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } bool first = true; for (QMap::ConstIterator it = d->parameterHash.constBegin(); it != d->parameterHash.constEnd(); ++it) { if (!first) { rv += "; "; } else { first = false; } if (isUsAscii(it.value())) { rv += it.key().toLatin1() + '='; QByteArray tmp = it.value().toLatin1(); addQuotes(tmp, true); // force quoting, eg. for whitespaces in parameter value rv += tmp; } else { if (useOutlookAttachmentEncoding()) { rv += it.key().toLatin1() + '='; qCDebug(KMIME_LOG) << "doing:" << it.value() << QLatin1String(d->encCS); rv += "\"" + encodeRFC2047String(it.value(), d->encCS) + "\""; } else { rv += it.key().toLatin1() + "*="; rv += encodeRFC2231String(it.value(), d->encCS); } } } return rv; } QString Parametrized::parameter(const QString &key) const { return d_func()->parameterHash.value(key.toLower()); } bool Parametrized::hasParameter(const QString &key) const { return d_func()->parameterHash.contains(key.toLower()); } void Parametrized::setParameter(const QString &key, const QString &value) { Q_D(Parametrized); d->parameterHash.insert(key.toLower(), value); } bool Parametrized::isEmpty() const { return d_func()->parameterHash.isEmpty(); } void Parametrized::clear() { Q_D(Parametrized); d->parameterHash.clear(); } bool Parametrized::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Parametrized); d->parameterHash.clear(); QByteArray charset; if (!parseParameterListWithCharset(scursor, send, d->parameterHash, charset, isCRLF)) { return false; } d->encCS = charset; return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(Ident, Address) kmime_mk_dptr_ctor(Ident, Address) //@endcond QByteArray Ident::as7BitString(bool withHeaderType) const { const Q_D(Ident); if (d->msgIdList.isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } for (const Types::AddrSpec &addr : qAsConst(d->msgIdList)) { if (!addr.isEmpty()) { const QString asString = addr.asString(); rv += '<'; if (!asString.isEmpty()) { rv += asString.toLatin1(); // FIXME: change parsing to use QByteArrays } rv += "> "; } } if (!rv.isEmpty()) { rv.resize(rv.length() - 1); } return rv; } void Ident::clear() { Q_D(Ident); d->msgIdList.clear(); d->cachedIdentifier.clear(); } bool Ident::isEmpty() const { return d_func()->msgIdList.isEmpty(); } bool Ident::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Ident); // msg-id := "<" id-left "@" id-right ">" // id-left := dot-atom-text / no-fold-quote / local-part // id-right := dot-atom-text / no-fold-literal / domain // // equivalent to: // msg-id := angle-addr d->msgIdList.clear(); d->cachedIdentifier.clear(); while (scursor != send) { eatCFWS(scursor, send, isCRLF); // empty entry ending the list: OK. if (scursor == send) { return true; } // empty entry: ignore. if (*scursor == ',') { scursor++; continue; } AddrSpec maybeMsgId; if (!parseAngleAddr(scursor, send, maybeMsgId, isCRLF)) { return false; } d->msgIdList.append(maybeMsgId); eatCFWS(scursor, send, isCRLF); // header end ending the list: OK. if (scursor == send) { return true; } // regular item separator: eat it. if (*scursor == ',') { scursor++; } } return true; } QVector Ident::identifiers() const { QVector rv; foreach (const Types::AddrSpec &addr, d_func()->msgIdList) { if (!addr.isEmpty()) { const QString asString = addr.asString(); if (!asString.isEmpty()) { rv.append(asString.toLatin1()); // FIXME: change parsing to use QByteArrays } } } return rv; } void Ident::fromIdent(const Ident* ident) { d_func()->encCS = ident->d_func()->encCS; d_func()->msgIdList = ident->d_func()->msgIdList; d_func()->cachedIdentifier = ident->d_func()->cachedIdentifier; } void Ident::appendIdentifier(const QByteArray &id) { Q_D(Ident); QByteArray tmp = id; if (!tmp.startsWith('<')) { tmp.prepend('<'); } if (!tmp.endsWith('>')) { tmp.append('>'); } AddrSpec msgId; const char *cursor = tmp.constData(); if (parseAngleAddr(cursor, cursor + tmp.length(), msgId)) { d->msgIdList.append(msgId); } else { qCWarning(KMIME_LOG) << "Unable to parse address spec!"; } } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(SingleIdent, Ident) kmime_mk_dptr_ctor(SingleIdent, Ident) //@endcond QByteArray SingleIdent::identifier() const { if (d_func()->msgIdList.isEmpty()) { return QByteArray(); } if (d_func()->cachedIdentifier.isEmpty()) { const Types::AddrSpec &addr = d_func()->msgIdList.first(); if (!addr.isEmpty()) { const QString asString = addr.asString(); if (!asString.isEmpty()) { d_func()->cachedIdentifier = asString.toLatin1();// FIXME: change parsing to use QByteArrays } } } return d_func()->cachedIdentifier; } void SingleIdent::setIdentifier(const QByteArray &id) { Q_D(SingleIdent); d->msgIdList.clear(); d->cachedIdentifier.clear(); appendIdentifier(id); } bool SingleIdent::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(SingleIdent); if (!Ident::parse(scursor, send, isCRLF)) { return false; } if (d->msgIdList.count() > 1) { KMIME_WARN << "more than one msg-id in header " << "allowing only a single one!" #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) << endl #else << Qt::endl #endif ; } return true; } //------------------------------ } // namespace Generics //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ReturnPath, Generics::Address, Return-Path) //@endcond QByteArray ReturnPath::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += '<' + d_func()->mailbox.as7BitString(d_func()->encCS) + '>'; return rv; } void ReturnPath::clear() { Q_D(ReturnPath); d->mailbox.setAddress(Types::AddrSpec()); d->mailbox.setName(QString()); } bool ReturnPath::isEmpty() const { const Q_D(ReturnPath); return !d->mailbox.hasAddress() && !d->mailbox.hasName(); } bool ReturnPath::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ReturnPath); eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } const char *oldscursor = scursor; Mailbox maybeMailbox; if (!parseMailbox(scursor, send, maybeMailbox, isCRLF)) { // mailbox parsing failed, but check for empty brackets: scursor = oldscursor; if (*scursor != '<') { return false; } scursor++; eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '>') { return false; } scursor++; // prepare a Null mailbox: AddrSpec emptyAddrSpec; maybeMailbox.setName(QString()); maybeMailbox.setAddress(emptyAddrSpec); } else { // check that there was no display-name: if (maybeMailbox.hasName()) { KMIME_WARN << "display-name \"" << maybeMailbox.name() << "\" in Return-Path!" #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) << endl #else << Qt::endl #endif ; } } d->mailbox = maybeMailbox; // see if that was all: eatCFWS(scursor, send, isCRLF); // and warn if it wasn't: if (scursor != send) { KMIME_WARN << "trailing garbage after angle-addr in Return-Path!" #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) << endl #else << Qt::endl #endif ; } return true; } //------------------------------ //------------------------------------ // NOTE: Do *not* register Generic with HeaderFactory, since its type() is changeable. Generic::Generic() : Generics::Unstructured(new GenericPrivate) { } Generic::Generic(const char *t, int len) : Generics::Unstructured(new GenericPrivate) { setType(t, len); } Generic::~Generic() { Q_D(Generic); delete d; d_ptr = nullptr; } void Generic::clear() { Q_D(Generic); delete[] d->type; d->type = nullptr; Unstructured::clear(); } bool Generic::isEmpty() const { return d_func()->type == nullptr || Unstructured::isEmpty(); } const char *Generic::type() const { return d_func()->type; } void Generic::setType(const char *type, int len) { Q_D(Generic); if (d->type) { delete[] d->type; } if (type) { const int l = (len < 0 ? strlen(type) : len) + 1; d->type = new char[l]; qstrncpy(d->type, type, l); } else { d->type = nullptr; } } //------------------------------------ //---------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name(MessageID, Generics::SingleIdent, Message-ID) //@endcond void MessageID::generate(const QByteArray &fqdn) { setIdentifier('<' + uniqueString() + '@' + fqdn + '>'); } //--------------------------------- //------------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Control, Generics::Structured, Control) //@endcond QByteArray Control::as7BitString(bool withHeaderType) const { const Q_D(Control); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += d->name; if (!d->parameter.isEmpty()) { rv += ' ' + d->parameter; } return rv; } void Control::clear() { Q_D(Control); d->name.clear(); d->parameter.clear(); } bool Control::isEmpty() const { return d_func()->name.isEmpty(); } QByteArray Control::controlType() const { return d_func()->name; } QByteArray Control::parameter() const { return d_func()->parameter; } bool Control::isCancel() const { return d_func()->name.toLower() == "cancel"; } void Control::setCancel(const QByteArray &msgid) { Q_D(Control); d->name = "cancel"; d->parameter = msgid; } bool Control::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Control); clear(); eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } const char *start = scursor; while (scursor != send && !isspace(*scursor)) { ++scursor; } d->name = QByteArray(start, scursor - start); eatCFWS(scursor, send, isCRLF); d->parameter = QByteArray(scursor, send - scursor); return true; } //----------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(MailCopiesTo, Generics::AddressList, Mail-Copies-To) //@endcond QByteArray MailCopiesTo::as7BitString(bool withHeaderType) const { QByteArray rv; if (withHeaderType) { rv += typeIntro(); } if (!AddressList::isEmpty()) { rv += AddressList::as7BitString(false); } else { if (d_func()->alwaysCopy) { rv += "poster"; } else if (d_func()->neverCopy) { rv += "nobody"; } } return rv; } QString MailCopiesTo::asUnicodeString() const { if (!AddressList::isEmpty()) { return AddressList::asUnicodeString(); } if (d_func()->alwaysCopy) { return QStringLiteral("poster"); } if (d_func()->neverCopy) { return QStringLiteral("nobody"); } return QString(); } void MailCopiesTo::clear() { Q_D(MailCopiesTo); AddressList::clear(); d->alwaysCopy = false; d->neverCopy = false; } bool MailCopiesTo::isEmpty() const { return AddressList::isEmpty() && !(d_func()->alwaysCopy || d_func()->neverCopy); } bool MailCopiesTo::alwaysCopy() const { return !AddressList::isEmpty() || d_func()->alwaysCopy; } void MailCopiesTo::setAlwaysCopy() { Q_D(MailCopiesTo); clear(); d->alwaysCopy = true; } bool MailCopiesTo::neverCopy() const { return d_func()->neverCopy; } void MailCopiesTo::setNeverCopy() { Q_D(MailCopiesTo); clear(); d->neverCopy = true; } bool MailCopiesTo::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(MailCopiesTo); clear(); if (send - scursor == 5) { if (qstrnicmp("never", scursor, 5) == 0) { d->neverCopy = true; return true; } } if (send - scursor == 6) { if (qstrnicmp("always", scursor, 6) == 0 || qstrnicmp("poster", scursor, 6) == 0) { d->alwaysCopy = true; return true; } if (qstrnicmp("nobody", scursor, 6) == 0) { d->neverCopy = true; return true; } } return AddressList::parse(scursor, send, isCRLF); } //------------------------------ //--------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Date, Generics::Structured, Date) //@endcond QByteArray Date::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } //QT5 fix port to QDateTime Qt::RFC2822Date is not enough we need to fix it. We need to use QLocale("C") + add "ddd, "; //rv += d_func()->dateTime.toString( Qt::RFC2822Date ).toLatin1(); rv += QLocale::c().toString(d_func()->dateTime, QStringLiteral("ddd, ")).toLatin1(); rv += d_func()->dateTime.toString(Qt::RFC2822Date).toLatin1(); return rv; } void Date::clear() { Q_D(Date); d->dateTime = QDateTime(); } bool Date::isEmpty() const { return d_func()->dateTime.isNull() || !d_func()->dateTime.isValid(); } QDateTime Date::dateTime() const { return d_func()->dateTime; } void Date::setDateTime(const QDateTime & dt) { Q_D(Date); d->dateTime = dt; } int Date::ageInDays() const { QDate today = QDate::currentDate(); return dateTime().date().daysTo(today); } bool Date::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Date); return parseDateTime(scursor, send, d->dateTime, isCRLF); } //-------------------------------------- //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Newsgroups, Generics::Structured, Newsgroups) kmime_mk_trivial_ctor_with_name(FollowUpTo, Newsgroups, Followup-To) //@endcond QByteArray Newsgroups::as7BitString(bool withHeaderType) const { const Q_D(Newsgroups); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } for (int i = 0; i < d->groups.count(); ++i) { rv += d->groups[ i ]; if (i != d->groups.count() - 1) { rv += ','; } } return rv; } void Newsgroups::fromUnicodeString(const QString & s, const QByteArray & b) { Q_UNUSED(b); Q_D(Newsgroups); from7BitString(s.toUtf8()); d->encCS = cachedCharset("UTF-8"); } QString Newsgroups::asUnicodeString() const { return QString::fromUtf8(as7BitString(false)); } void Newsgroups::clear() { Q_D(Newsgroups); d->groups.clear(); } bool Newsgroups::isEmpty() const { return d_func()->groups.isEmpty(); } QVector Newsgroups::groups() const { return d_func()->groups; } void Newsgroups::setGroups(const QVector &groups) { Q_D(Newsgroups); d->groups = groups; } bool Newsgroups::isCrossposted() const { return d_func()->groups.count() >= 2; } bool Newsgroups::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Newsgroups); clear(); forever { eatCFWS(scursor, send, isCRLF); if (scursor != send && *scursor == ',') { ++scursor; } eatCFWS(scursor, send, isCRLF); if (scursor == send) { return true; } const char *start = scursor; while (scursor != send && !isspace(*scursor) && *scursor != ',') { ++scursor; } QByteArray group(start, scursor - start); d->groups.append(group); } return true; } //-------------------------------- //-------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Lines, Generics::Structured, Lines) //@endcond QByteArray Lines::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray num; num.setNum(d_func()->lines); if (withHeaderType) { return typeIntro() + num; } return num; } QString Lines::asUnicodeString() const { if (isEmpty()) { return QString(); } return QString::number(d_func()->lines); } void Lines::clear() { Q_D(Lines); d->lines = -1; } bool Lines::isEmpty() const { return d_func()->lines == -1; } int Lines::numberOfLines() const { return d_func()->lines; } void Lines::setNumberOfLines(int lines) { Q_D(Lines); d->lines = lines; } bool Lines::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Lines); eatCFWS(scursor, send, isCRLF); if (parseDigits(scursor, send, d->lines) == 0) { clear(); return false; } return true; } //------------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ContentType, Generics::Parametrized, Content-Type) //@endcond bool ContentType::isEmpty() const { return d_func()->mimeType.isEmpty(); } void ContentType::clear() { Q_D(ContentType); d->category = CCsingle; d->mimeType.clear(); Parametrized::clear(); } QByteArray ContentType::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += mimeType(); if (!Parametrized::isEmpty()) { rv += "; " + Parametrized::as7BitString(false); } return rv; } QByteArray ContentType::mimeType() const { Q_D(const ContentType); return d->mimeType; } QByteArray ContentType::mediaType() const { Q_D(const ContentType); const int pos = d->mimeType.indexOf('/'); if (pos < 0) { return d->mimeType; } else { return d->mimeType.left(pos); } } QByteArray ContentType::subType() const { Q_D(const ContentType); const int pos = d->mimeType.indexOf('/'); if (pos < 0) { return QByteArray(); } else { return d->mimeType.mid(pos + 1); } } void ContentType::setMimeType(const QByteArray & mimeType) { Q_D(ContentType); d->mimeType = mimeType; if (isMultipart()) { d->category = CCcontainer; } else { d->category = CCsingle; } } bool ContentType::isMediatype(const char *mediatype) const { Q_D(const ContentType); const int len = strlen(mediatype); return qstrnicmp(d->mimeType.constData(), mediatype, len) == 0 && (d->mimeType.at(len) == '/' || d->mimeType.size() == len); } bool ContentType::isSubtype(const char *subtype) const { Q_D(const ContentType); const int pos = d->mimeType.indexOf('/'); if (pos < 0) { return false; } const int len = strlen(subtype); return qstrnicmp(d->mimeType.constData() + pos + 1, subtype, len) == 0 && d->mimeType.size() == pos + len + 1; } bool ContentType::isMimeType(const char* mimeType) const { Q_D(const ContentType); return qstricmp(d->mimeType.constData(), mimeType) == 0; } bool ContentType::isText() const { return (isMediatype("text") || isEmpty()); } bool ContentType::isPlainText() const { return (qstricmp(d_func()->mimeType.constData(), "text/plain") == 0 || isEmpty()); } bool ContentType::isHTMLText() const { return qstricmp(d_func()->mimeType.constData(), "text/html") == 0; } bool ContentType::isImage() const { return isMediatype("image"); } bool ContentType::isMultipart() const { return isMediatype("multipart"); } bool ContentType::isPartial() const { return qstricmp(d_func()->mimeType.constData(), "message/partial") == 0; } QByteArray ContentType::charset() const { QByteArray ret = parameter(QStringLiteral("charset")).toLatin1(); if (ret.isEmpty()) { //return the default-charset if necessary ret = Content::defaultCharset(); } return ret; } void ContentType::setCharset(const QByteArray & s) { setParameter(QStringLiteral("charset"), QString::fromLatin1(s)); } QByteArray ContentType::boundary() const { return parameter(QStringLiteral("boundary")).toLatin1(); } void ContentType::setBoundary(const QByteArray & s) { setParameter(QStringLiteral("boundary"), QString::fromLatin1(s)); } QString ContentType::name() const { return parameter(QStringLiteral("name")); } void ContentType::setName(const QString & s, const QByteArray & cs) { Q_D(ContentType); d->encCS = cs; setParameter(QStringLiteral("name"), s); } QByteArray ContentType::id() const { return parameter(QStringLiteral("id")).toLatin1(); } void ContentType::setId(const QByteArray & s) { setParameter(QStringLiteral("id"), QString::fromLatin1(s)); } int ContentType::partialNumber() const { QByteArray p = parameter(QStringLiteral("number")).toLatin1(); if (!p.isEmpty()) { return p.toInt(); } else { return -1; } } int ContentType::partialCount() const { QByteArray p = parameter(QStringLiteral("total")).toLatin1(); if (!p.isEmpty()) { return p.toInt(); } else { return -1; } } contentCategory ContentType::category() const { return d_func()->category; } void ContentType::setCategory(contentCategory c) { Q_D(ContentType); d->category = c; } void ContentType::setPartialParams(int total, int number) { setParameter(QStringLiteral("number"), QString::number(number)); setParameter(QStringLiteral("total"), QString::number(total)); } bool ContentType::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentType); // content-type: type "/" subtype *(";" parameter) clear(); eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; // empty header } // type QPair maybeMimeType; if (!parseToken(scursor, send, maybeMimeType, ParseTokenNoFlag)) { return false; } // subtype eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '/') { return false; } scursor++; eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } QPair maybeSubType; if (!parseToken(scursor, send, maybeSubType, ParseTokenNoFlag)) { return false; } d->mimeType.reserve(maybeMimeType.second + maybeSubType.second + 1); d->mimeType = QByteArray(maybeMimeType.first, maybeMimeType.second).toLower() + '/' + QByteArray(maybeSubType.first, maybeSubType.second).toLower(); // parameter list eatCFWS(scursor, send, isCRLF); if (scursor == send) { goto success; // no parameters } if (*scursor != ';') { return false; } scursor++; if (!Parametrized::parse(scursor, send, isCRLF)) { return false; } // adjust category success: if (isMultipart()) { d->category = CCcontainer; } else { d->category = CCsingle; } return true; } //------------------------------ //--------------------------- kmime_mk_trivial_ctor_with_name_and_dptr(ContentID, SingleIdent, Content-ID) kmime_mk_dptr_ctor(ContentID, SingleIdent) bool ContentID::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentID); // Content-id := "<" contentid ">" // contentid := now whitespaces const char *origscursor = scursor; if (!SingleIdent::parse(scursor, send, isCRLF)) { scursor = origscursor; d->msgIdList.clear(); d->cachedIdentifier.clear(); while (scursor != send) { eatCFWS(scursor, send, isCRLF); // empty entry ending the list: OK. if (scursor == send) { return true; } // empty entry: ignore. if (*scursor == ',') { scursor++; continue; } AddrSpec maybeContentId; // Almost parseAngleAddr if (scursor == send || *scursor != '<') { return false; } scursor++; // eat '<' eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // Save chars untill '>'' QString result; if (!parseDotAtom(scursor, send, result, false)) { return false; } eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '>') { return false; } scursor++; // /Almost parseAngleAddr maybeContentId.localPart = result; d->msgIdList.append(maybeContentId); eatCFWS(scursor, send, isCRLF); // header end ending the list: OK. if (scursor == send) { return true; } // regular item separator: eat it. if (*scursor == ',') { scursor++; } } return true; } else { return true; } } //--------------------------- //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ContentTransferEncoding, Generics::Token, Content-Transfer-Encoding) //@endcond typedef struct { const char *s; int e; } encTableType; static const encTableType encTable[] = { { "7Bit", CE7Bit }, { "8Bit", CE8Bit }, { "quoted-printable", CEquPr }, { "base64", CEbase64 }, { "x-uuencode", CEuuenc }, { "binary", CEbinary }, { nullptr, 0} }; void ContentTransferEncoding::clear() { Q_D(ContentTransferEncoding); d->decoded = true; d->cte = CE7Bit; Token::clear(); } contentEncoding ContentTransferEncoding::encoding() const { return d_func()->cte; } void ContentTransferEncoding::setEncoding(contentEncoding e) { Q_D(ContentTransferEncoding); d->cte = e; for (int i = 0; encTable[i].s != nullptr; ++i) { if (d->cte == encTable[i].e) { setToken(encTable[i].s); break; } } } bool ContentTransferEncoding::isDecoded() const { return d_func()->decoded; } void ContentTransferEncoding::setDecoded(bool decoded) { Q_D(ContentTransferEncoding); d->decoded = decoded; } bool ContentTransferEncoding::needToEncode() const { const Q_D(ContentTransferEncoding); return d->decoded && (d->cte == CEquPr || d->cte == CEbase64); } bool ContentTransferEncoding::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentTransferEncoding); clear(); if (!Token::parse(scursor, send, isCRLF)) { return false; } // TODO: error handling in case of an unknown encoding? for (int i = 0; encTable[i].s != nullptr; ++i) { if (qstricmp(token().constData(), encTable[i].s) == 0) { d->cte = (contentEncoding)encTable[i].e; break; } } d->decoded = (d->cte == CE7Bit || d->cte == CE8Bit); return true; } //-------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ContentDisposition, Generics::Parametrized, Content-Disposition) //@endcond QByteArray ContentDisposition::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } if (d_func()->disposition == CDattachment) { rv += "attachment"; } else if (d_func()->disposition == CDinline) { rv += "inline"; } else { return QByteArray(); } if (!Parametrized::isEmpty()) { rv += "; " + Parametrized::as7BitString(false); } return rv; } bool ContentDisposition::isEmpty() const { return d_func()->disposition == CDInvalid; } void ContentDisposition::clear() { Q_D(ContentDisposition); d->disposition = CDInvalid; Parametrized::clear(); } contentDisposition ContentDisposition::disposition() const { return d_func()->disposition; } void ContentDisposition::setDisposition(contentDisposition disp) { Q_D(ContentDisposition); d->disposition = disp; } QString KMime::Headers::ContentDisposition::filename() const { return parameter(QStringLiteral("filename")); } void ContentDisposition::setFilename(const QString & filename) { setParameter(QStringLiteral("filename"), filename); } bool ContentDisposition::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentDisposition); clear(); // token QByteArray token; eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } QPair maybeToken; if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) { return false; } token = QByteArray(maybeToken.first, maybeToken.second).toLower(); if (token == "inline") { d->disposition = CDinline; } else if (token == "attachment") { d->disposition = CDattachment; } else { return false; } // parameter list eatCFWS(scursor, send, isCRLF); if (scursor == send) { return true; // no parameters } if (*scursor != ';') { return false; } scursor++; return Parametrized::parse(scursor, send, isCRLF); } //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name(Subject, Generics::Unstructured, Subject) //@endcond Base *createHeader(const QByteArray & type) { return HeaderFactory::createHeader(type); } //@cond PRIVATE kmime_mk_trivial_ctor_with_name(ContentDescription, Generics::Unstructured, Content-Description) kmime_mk_trivial_ctor_with_name(ContentLocation, Generics::Unstructured, Content-Location) kmime_mk_trivial_ctor_with_name(From, Generics::MailboxList, From) kmime_mk_trivial_ctor_with_name(Sender, Generics::SingleMailbox, Sender) kmime_mk_trivial_ctor_with_name(To, Generics::AddressList, To) kmime_mk_trivial_ctor_with_name(Cc, Generics::AddressList, Cc) kmime_mk_trivial_ctor_with_name(Bcc, Generics::AddressList, Bcc) kmime_mk_trivial_ctor_with_name(ReplyTo, Generics::AddressList, Reply-To) kmime_mk_trivial_ctor_with_name(Keywords, Generics::PhraseList, Keywords) kmime_mk_trivial_ctor_with_name(MIMEVersion, Generics::DotAtom, MIME-Version) kmime_mk_trivial_ctor_with_name(Supersedes, Generics::SingleIdent, Supersedes) kmime_mk_trivial_ctor_with_name(InReplyTo, Generics::Ident, In-Reply-To) kmime_mk_trivial_ctor_with_name(References, Generics::Ident, References) kmime_mk_trivial_ctor_with_name(Organization, Generics::Unstructured, Organization) kmime_mk_trivial_ctor_with_name(UserAgent, Generics::Unstructured, User-Agent) //@endcond } // namespace Headers } // namespace KMime diff --git a/tests/test_kmime_header_parsing.cpp b/tests/test_kmime_header_parsing.cpp index a3ee2fc..c620c15 100644 --- a/tests/test_kmime_header_parsing.cpp +++ b/tests/test_kmime_header_parsing.cpp @@ -1,474 +1,474 @@ #include "kmime_headers.h" #include "kmime_header_parsing.h" #include #include #include //#include //#include #include #include #include #include using namespace KMime::HeaderParsing; using namespace std; static const char *tokenTypes[] = { "encoded-word", "atom", "token", "quoted-string", "domain-literal", "comment", "phrase", "dot-atom", "domain", "obs-route", "addr-spec", "angle-addr", "mailbox", "group", "address", "address-list", "parameter", "raw-parameter-list", "parameter-list", "time", "date-time" }; static const int tokenTypesLen = sizeof tokenTypes / sizeof * tokenTypes; void usage(const char *msg = nullptr) { if (msg && *msg) { cerr << msg << endl; } cerr << "usage: test_kmime_header_parsing " "(--token |--headerfield |--header)\n" "\n" " --token interpret input as and output\n" " (-t) in parsed form. Currently defined values of\n" " are:" << endl; for (int i = 0 ; i < tokenTypesLen ; ++i) { cerr << " " << tokenTypes[i] << endl; } cerr << "\n" " --headerfield interpret input as header field \n" " (-f) and output in parsed form.\n" "\n" " --header parse an RFC2822 header. Iterates over all\n" " (-h) header fields and outputs them in parsed form." << endl; exit(1); } ostream &operator<<(ostream &stream, const QString &str) { return stream << str.toUtf8().data(); } int main(int argc, char *argv[]) { if (argc == 1 || argc > 3) { usage(); } // // process options: // enum { None, Token, HeaderField, Header } action = None; const char *argument = nullptr; bool withCRLF = false; while (true) { int option_index = 0; static const struct option long_options[] = { // actions: { "token", 1, nullptr, 't' }, { "headerfield", 1, nullptr, 'f' }, { "header", 0, nullptr, 'h' }, { "crlf", 0, nullptr, 'c' }, { nullptr, 0, nullptr, 0 } }; int c = getopt_long(argc, argv, "cf:ht:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'c': // --crlf withCRLF = true; break; case 't': // --token action = Token; argument = optarg; break; case 'f': // --headerfield usage("--headerfield is not yet implemented!"); break; case 'h': // --header usage("--header is not yet implemented!"); break; default: usage("unknown option encountered!"); } } if (optind < argc) { usage("non-option argument encountered!"); } assert(action == Token); int index; for (index = 0 ; index < tokenTypesLen ; ++index) { if (!qstricmp(tokenTypes[index], argument)) { break; } } if (index >= tokenTypesLen) { usage("unknown token type"); } //QT5 KComponentData componentData( "test_kmime_header_parsing" ); QFile stdIn; stdIn.open(stdin, QIODevice::ReadOnly); const QByteArray indata = stdIn.readAll(); stdIn.close(); QByteArray::ConstIterator iit = indata.begin(); const QByteArray::ConstIterator iend = indata.end(); switch (index) { case 0: { // encoded-word QString result; QByteArray language, charset; // must have checked for initial '=' already: bool ok = indata.size() >= 1 && *iit++ == '=' && parseEncodedWord(iit, iend, result, language, charset); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl << "language:\n" << language.data() << endl; } break; case 1: { // atom QString result = QStringLiteral("with 8bit: "); bool ok = parseAtom(iit, iend, result, true); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; result = QStringLiteral("without 8bit: "); #ifdef COMPILE_FAIL ok = parseAtom(indata.begin(), iend, result, false); #else iit = indata.begin(); ok = parseAtom(iit, iend, result, false); #endif cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 2: { // token QString result = QStringLiteral("with 8bit: "); bool ok = parseToken(iit, iend, result, ParseTokenAllow8Bit); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; result = QStringLiteral("without 8bit: "); #ifdef COMPILE_FAIL ok = parseToken(indata.begin(), iend, result, ParseTokenNoFlag); #else iit = indata.begin(); ok = parseToken(iit, iend, result, ParseTokenNoFlag); #endif cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 3: { // quoted-string QString result; // must have checked for initial '"' already: bool ok = *iit++ == '"' && parseGenericQuotedString(iit, iend, result, withCRLF, '"', '"'); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 4: { // domain-literal QString result; // must have checked for initial '[' already: bool ok = *iit++ == '[' && parseGenericQuotedString(iit, iend, result, withCRLF, '[', ']'); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 5: { // comment QString result; // must have checked for initial '(' already: bool ok = *iit++ == '(' && parseComment(iit, iend, result, withCRLF, true); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 6: { // phrase QString result; bool ok = parsePhrase(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 7: { // dot-atom QString result; bool ok = parseDotAtom(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 8: { // domain QString result; bool ok = parseDomain(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result:\n" << result << endl; } break; case 9: { // obs-route QStringList result; bool ok = parseObsRoute(iit, iend, result, withCRLF, true /*save*/); cout << (ok ? "OK" : "BAD") << endl << "result: " << result.count() << " domains:" << endl; for (QStringList::ConstIterator it = result.constBegin() ; it != result.constEnd() ; ++it) { cout << (*it) << endl; } } break; case 10: { // addr-spec KMime::Types::AddrSpec result; bool ok = parseAddrSpec(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result.localPart:\n" << result.localPart << endl << "result.domain:\n" << result.domain << endl; } break; case 11: { // angle-addr KMime::Types::AddrSpec result; bool ok = parseAngleAddr(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result.localPart:\n" << result.localPart << endl << "result.domain:\n" << result.domain << endl; } break; case 12: { // mailbox KMime::Types::Mailbox result; bool ok = parseMailbox(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result.displayName:\n" << result.name() << endl << "result.addrSpec.localPart:\n" << result.addrSpec().localPart << endl << "result.addrSpec.domain:\n" << result.addrSpec().domain << endl; } break; case 13: { // group KMime::Types::Address result; bool ok = parseGroup(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result.displayName:\n" << result.displayName << endl; int i = 0; - foreach (const auto &it, result.mailboxList) { + for (const auto &it : qAsConst(result.mailboxList)) { cout << "result.mailboxList[" << i << "].displayName:\n" << (it).name() << endl << "result.mailboxList[" << i << "].addrSpec.localPart:\n" << (it).addrSpec().localPart << endl << "result.mailboxList[" << i << "].addrSpec.domain:\n" << (it).addrSpec().domain << endl; ++i; } } break; case 14: { // address KMime::Types::Address result; bool ok = parseAddress(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result.displayName:\n" << endl; int i = 0; foreach (const auto &it, result.mailboxList) { cout << "result.mailboxList[" << i << "].displayName:\n" << (it).name() << endl << "result.mailboxList[" << i << "].addrSpec.localPart:\n" << (it).addrSpec().localPart << endl << "result.mailboxList[" << i << "].addrSpec.domain:\n" << (it).addrSpec().domain << endl; ++i; } } break; case 15: { // address-list KMime::Types::AddressList result; bool ok = parseAddressList(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl; int j = 0; - foreach (auto jt, result) { + for (const auto &jt : qAsConst(result)) { cout << "result[" << j << "].displayName:\n" << (jt).displayName << endl; int i = 0; foreach (const auto &it, (jt).mailboxList) { cout << "result[" << j << "].mailboxList[" << i << "].displayName:\n" << (it).name() << endl << "result[" << j << "].mailboxList[" << i << "].addrSpec.localPart:\n" << (it).addrSpec().localPart << endl << "result[" << j << "].mailboxList[" << i << "].addrSpec.domain:\n" << (it).addrSpec().domain << endl; ++i; } ++j; } } break; case 16: { // parameter QPair result; bool ok = parseParameter(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result.first (attribute):\n" << result.first << endl << "result.second.qstring (value):\n" << result.second.qstring << endl << "result.second.qpair (value):\n" << QByteArray(result.second.qpair.first, result.second.qpair.second + 1).data() << endl; } break; case 17: { // raw-parameter-list QMap result; bool ok = parseRawParameterList(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result: " << result.count() << " raw parameters:" << endl; int i = 0; for (QMap::ConstIterator it = result.constBegin(); it != result.constEnd() ; ++it, ++i) { cout << "result[" << i << "].key() (attribute):\n" << it.key() << endl << "result[" << i << "].data().qstring (value):\n" << it.value().qstring << endl << "result[" << i << "].data().qpair (value):\n" << QByteArray(it.value().qpair.first, it.value().qpair.second + 1).data() << endl; } } break; case 18: { // parameter-list QMap result; bool ok = parseParameterList(iit, iend, result, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result: " << result.count() << " parameters:" << endl; int i = 0; for (QMap::Iterator it = result.begin() ; it != result.end() ; ++it, ++i) { cout << "result[" << i << "].key() (attribute):\n" << it.key() << endl << "result[" << i << "].data() (value):\n" << it.value() << endl; } } break; case 19: { // time int hour, mins, secs; long int secsEastOfGMT; bool timeZoneKnown = true; bool ok = parseTime(iit, iend, hour, mins, secs, secsEastOfGMT, timeZoneKnown, withCRLF); cout << (ok ? "OK" : "BAD") << endl << "result.hour: " << hour << endl << "result.mins: " << mins << endl << "result.secs: " << secs << endl << "result.secsEastOfGMT: " << secsEastOfGMT << endl << "result.timeZoneKnown: " << timeZoneKnown << endl; } break; case 20: { // date-time QDateTime result; bool ok = parseDateTime(iit, iend, result, withCRLF); time_t timet = result.toSecsSinceEpoch(); cout << (ok ? "OK" : "BAD") << endl << "result.time (in local timezone): " << ctime(&timet) << "result.secsEastOfGMT: " << result.offsetFromUtc() << " (" << result.offsetFromUtc() / 60 << "mins)" << endl; } break; default: assert(0); } }