File indexing completed on 2022-11-29 19:53:39

0001 /*  -*- c++ -*-
0002     kmime_headers.cpp
0003 
0004     KMime, the KDE Internet mail/usenet news message library.
0005     SPDX-FileCopyrightText: 2001-2002 the KMime authors.
0006     See file AUTHORS for details
0007     SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 /**
0012   @file
0013   This file is part of the API for handling @ref MIME data and
0014   defines the various header classes:
0015    - header's base class defining the common interface
0016    - generic base classes for different types of fields
0017    - incompatible, Structured-based field classes
0018    - compatible, Unstructured-based field classes
0019 
0020   @brief
0021   Defines the various headers classes.
0022 
0023   @authors the KMime authors (see AUTHORS file),
0024   Volker Krause \<vkrause@kde.org\>
0025 */
0026 
0027 #include "kmime_headers.h"
0028 #include "kmime_headers_p.h"
0029 
0030 #include "kmime_util.h"
0031 #include "kmime_util_p.h"
0032 #include "kmime_codecs.h"
0033 #include "kmime_content.h"
0034 #include "kmime_headerfactory_p.h"
0035 #include "kmime_debug.h"
0036 #include "kmime_warning.h"
0037 
0038 #include <KCharsets>
0039 #include <KCodecs>
0040 
0041 #include <cassert>
0042 #include <cctype>
0043 
0044 // macro to generate a default constructor implementation
0045 #define kmime_mk_trivial_ctor( subclass, baseclass )                  \
0046     subclass::subclass() : baseclass()           \
0047     {                                                                     \
0048     }                                                                     \
0049     \
0050     subclass::~subclass() {}
0051 
0052 // end kmime_mk_trivial_ctor
0053 
0054 #define kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \
0055     subclass::subclass() : baseclass( new subclass##Private ) \
0056     {                                                                     \
0057     }                                                                     \
0058     \
0059     subclass::~subclass() { \
0060         Q_D(subclass); \
0061         delete d;  /* see comment above the BasePrivate class */ \
0062         d_ptr = nullptr; \
0063     }
0064 
0065 // end kmime_mk_trivial_ctor_with_dptr
0066 
0067 #define kmime_mk_trivial_ctor_with_name( subclass, baseclass, name )  \
0068     kmime_mk_trivial_ctor( subclass, baseclass )                          \
0069     \
0070     const char *subclass::type() const                                    \
0071     {                                                                     \
0072         return staticType();                                                \
0073     }                                                                     \
0074     const char *subclass::staticType() { return #name; }
0075 
0076 #define kmime_mk_trivial_ctor_with_name_and_dptr( subclass, baseclass, name ) \
0077     kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \
0078     const char *subclass::type() const { return staticType(); } \
0079     const char *subclass::staticType() { return #name; }
0080 
0081 #define kmime_mk_dptr_ctor( subclass, baseclass ) \
0082     subclass::subclass( subclass##Private *d ) : baseclass( d ) {}
0083 
0084 using namespace KMime;
0085 using namespace KMime::Headers;
0086 using namespace KMime::Types;
0087 using namespace KMime::HeaderParsing;
0088 
0089 namespace KMime
0090 {
0091 namespace Headers
0092 {
0093 //-----<Base>----------------------------------
0094 Base::Base() : d_ptr(new BasePrivate)
0095 {
0096 }
0097 
0098 Base::Base(BasePrivate *dd) :
0099     d_ptr(dd)
0100 {
0101 }
0102 
0103 Base::~Base()
0104 {
0105     delete d_ptr;
0106     d_ptr = nullptr;
0107 }
0108 
0109 void Base::from7BitString(const char *s, size_t len)
0110 {
0111     from7BitString(QByteArray::fromRawData(s, len));
0112 }
0113 
0114 QByteArray Base::rfc2047Charset() const
0115 {
0116     if (d_ptr->encCS.isEmpty()) {
0117         return Content::defaultCharset();
0118     } else {
0119         return d_ptr->encCS;
0120     }
0121 }
0122 
0123 void Base::setRFC2047Charset(const QByteArray &cs)
0124 {
0125     d_ptr->encCS = cachedCharset(cs);
0126 }
0127 
0128 const char *Base::type() const
0129 {
0130     return "";
0131 }
0132 
0133 bool Base::is(const char *t) const
0134 {
0135     return qstricmp(t, type()) == 0;
0136 }
0137 
0138 bool Base::isMimeHeader() const
0139 {
0140     return qstrnicmp(type(), "Content-", 8) == 0;
0141 }
0142 
0143 QByteArray Base::typeIntro() const
0144 {
0145     return QByteArray(type()) + ": ";
0146 }
0147 
0148 //-----</Base>---------------------------------
0149 
0150 namespace Generics
0151 {
0152 
0153 //-----<Unstructured>-------------------------
0154 
0155 //@cond PRIVATE
0156 kmime_mk_dptr_ctor(Unstructured, Base)
0157 //@endcond
0158 
0159 Unstructured::Unstructured() : Base(new UnstructuredPrivate)
0160 {
0161 }
0162 
0163 Unstructured::~Unstructured()
0164 {
0165     Q_D(Unstructured);
0166     delete d;
0167     d_ptr = nullptr;
0168 }
0169 
0170 void Unstructured::from7BitString(const QByteArray &s)
0171 {
0172     Q_D(Unstructured);
0173     d->decoded = KCodecs::decodeRFC2047String(s, &d->encCS, Content::defaultCharset());
0174 }
0175 
0176 QByteArray Unstructured::as7BitString(bool withHeaderType) const
0177 {
0178     const Q_D(Unstructured);
0179     QByteArray result;
0180     if (withHeaderType) {
0181         result = typeIntro();
0182     }
0183     result += encodeRFC2047String(d->decoded, d->encCS) ;
0184 
0185     return result;
0186 }
0187 
0188 void Unstructured::fromUnicodeString(const QString &s, const QByteArray &b)
0189 {
0190     Q_D(Unstructured);
0191     d->decoded = s;
0192     d->encCS = cachedCharset(b);
0193 }
0194 
0195 QString Unstructured::asUnicodeString() const
0196 {
0197     return d_func()->decoded;
0198 }
0199 
0200 void Unstructured::clear()
0201 {
0202     Q_D(Unstructured);
0203     d->decoded.truncate(0);
0204 }
0205 
0206 bool Unstructured::isEmpty() const
0207 {
0208     return d_func()->decoded.isEmpty();
0209 }
0210 
0211 //-----</Unstructured>-------------------------
0212 
0213 //-----<Structured>-------------------------
0214 
0215 Structured::Structured() : Base(new StructuredPrivate)
0216 {
0217 }
0218 
0219 kmime_mk_dptr_ctor(Structured, Base)
0220 
0221 Structured::~Structured()
0222 {
0223     Q_D(Structured);
0224     delete d;
0225     d_ptr = nullptr;
0226 }
0227 
0228 
0229 void Structured::from7BitString(const char *s, size_t len)
0230 {
0231     Q_D(Structured);
0232     if (d->encCS.isEmpty()) {
0233         d->encCS = Content::defaultCharset();
0234     }
0235     parse(s, s + len);
0236 }
0237 
0238 void Structured::from7BitString(const QByteArray &s)
0239 {
0240 #if 0
0241     Q_D(Structured);
0242     //Bug about mailto with space which are replaced by "_" so it failed to parse
0243     //=> we reconvert to correct encoding as RFC2047
0244     const QString str = KCodecs::decodeRFC2047String(s, &d->encCS, Content::defaultCharset());
0245     const QByteArray ba = KCodecs::encodeRFC2047String(str, d->encCS);
0246     from7BitString(ba.constData(), ba.length());
0247 #else
0248     from7BitString(s.constData(), s.length());
0249 #endif
0250 }
0251 
0252 QString Structured::asUnicodeString() const
0253 {
0254     return QString::fromLatin1(as7BitString(false));
0255 }
0256 
0257 void Structured::fromUnicodeString(const QString &s, const QByteArray &b)
0258 {
0259     Q_D(Structured);
0260     d->encCS = cachedCharset(b);
0261     from7BitString(s.toLatin1());
0262 }
0263 
0264 //-----</Structured>-------------------------
0265 
0266 //-----<Address>-------------------------
0267 
0268 Address::Address() : Structured(new AddressPrivate)
0269 {
0270 }
0271 
0272 kmime_mk_dptr_ctor(Address, Structured)
0273 
0274     Address::~Address() = default;
0275 
0276 // helper method used in AddressList and MailboxList
0277 static bool stringToMailbox(const QByteArray &address,
0278                             const QString &displayName, Types::Mailbox &mbox)
0279 {
0280     Types::AddrSpec addrSpec;
0281     mbox.setName(displayName);
0282     const char *cursor = address.constData();
0283     if (!parseAngleAddr(cursor, cursor + address.length(), addrSpec)) {
0284         if (!parseAddrSpec(cursor, cursor + address.length(), addrSpec)) {
0285             qCWarning(KMIME_LOG) << "stringToMailbox: Invalid address";
0286             return false;
0287         }
0288     }
0289     mbox.setAddress(addrSpec);
0290     return true;
0291 }
0292 
0293 //-----</Address>-------------------------
0294 
0295 //-----<MailboxList>-------------------------
0296 
0297 kmime_mk_trivial_ctor_with_dptr(MailboxList, Address)
0298 kmime_mk_dptr_ctor(MailboxList, Address)
0299 
0300 QByteArray MailboxList::as7BitString(bool withHeaderType) const
0301 {
0302     const Q_D(MailboxList);
0303     if (isEmpty()) {
0304       return {};
0305     }
0306 
0307     QByteArray rv;
0308     if (withHeaderType) {
0309         rv = typeIntro();
0310     }
0311     for (const Types::Mailbox &mbox : std::as_const(d->mailboxList)) {
0312         rv += mbox.as7BitString(d->encCS);
0313         rv += ", ";
0314     }
0315     rv.resize(rv.length() - 2);
0316     return rv;
0317 }
0318 
0319 void MailboxList::fromUnicodeString(const QString &s, const QByteArray &b)
0320 {
0321     Q_D(MailboxList);
0322     d->encCS = cachedCharset(b);
0323     from7BitString(encodeRFC2047Sentence(s, b));
0324 }
0325 
0326 QString MailboxList::asUnicodeString() const
0327 {
0328     Q_D(const MailboxList);
0329     return Mailbox::listToUnicodeString(d->mailboxList);
0330 }
0331 
0332 void MailboxList::clear()
0333 {
0334     Q_D(MailboxList);
0335     d->mailboxList.clear();
0336 }
0337 
0338 bool MailboxList::isEmpty() const
0339 {
0340     return d_func()->mailboxList.isEmpty();
0341 }
0342 
0343 void MailboxList::addAddress(const Types::Mailbox &mbox)
0344 {
0345     Q_D(MailboxList);
0346     d->mailboxList.append(mbox);
0347 }
0348 
0349 void MailboxList::addAddress(const QByteArray &address,
0350                              const QString &displayName)
0351 {
0352     Q_D(MailboxList);
0353     Types::Mailbox mbox;
0354     if (stringToMailbox(address, displayName, mbox)) {
0355         d->mailboxList.append(mbox);
0356     }
0357 }
0358 
0359 QVector<QByteArray> MailboxList::addresses() const
0360 {
0361     QVector<QByteArray> rv;
0362     rv.reserve(d_func()->mailboxList.count());
0363     const auto mailboxList = d_func()->mailboxList;
0364     for (const Types::Mailbox &mbox : mailboxList) {
0365         rv.append(mbox.address());
0366     }
0367     return rv;
0368 }
0369 
0370 QStringList MailboxList::displayNames() const
0371 {
0372     Q_D(const MailboxList);
0373     QStringList rv;
0374     rv.reserve(d->mailboxList.count());
0375     for (const Types::Mailbox &mbox : std::as_const(d->mailboxList)) {
0376         if (mbox.hasName()) {
0377             rv.append(mbox.name());
0378         } else {
0379             rv.append(QString::fromLatin1(mbox.address()));
0380         }
0381     }
0382     return rv;
0383 }
0384 
0385 QString MailboxList::displayString() const
0386 {
0387     Q_D(const MailboxList);
0388     if (d->mailboxList.size() == 1) { // fast-path to avoid temporary QStringList in the common case of just one From address
0389         const auto& mbox = d->mailboxList.at(0);
0390         if (mbox.hasName()) {
0391             return mbox.name();
0392         } else {
0393             return QString::fromLatin1(mbox.address());
0394         }
0395     }
0396     return displayNames().join(QLatin1String(", "));
0397 }
0398 
0399 Types::Mailbox::List MailboxList::mailboxes() const
0400 {
0401     return d_func()->mailboxList;
0402 }
0403 
0404 bool MailboxList::parse(const char *&scursor, const char *const send,
0405                         bool isCRLF)
0406 {
0407     Q_D(MailboxList);
0408     // examples:
0409     // from := "From:" mailbox-list CRLF
0410     // sender := "Sender:" mailbox CRLF
0411 
0412     // parse an address-list:
0413     QVector<Types::Address> maybeAddressList;
0414     if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) {
0415         return false;
0416     }
0417 
0418     d->mailboxList.clear();
0419     d->mailboxList.reserve(maybeAddressList.count());
0420 
0421     // extract the mailboxes and complain if there are groups:
0422     for (const auto &it : std::as_const(maybeAddressList)) {
0423         if (!(it).displayName.isEmpty()) {
0424             KMIME_WARN << "mailbox groups in header disallowing them! Name: \""
0425                        << (it).displayName << "\""
0426                        << Qt::endl
0427                           ;
0428         }
0429         d->mailboxList += (it).mailboxList;
0430     }
0431     return true;
0432 }
0433 
0434 //-----</MailboxList>-------------------------
0435 
0436 //-----<SingleMailbox>-------------------------
0437 
0438 //@cond PRIVATE
0439 kmime_mk_trivial_ctor_with_dptr(SingleMailbox, MailboxList)
0440 //@endcond
0441 
0442 bool SingleMailbox::parse(const char *&scursor, const char *const send,
0443                           bool isCRLF)
0444 {
0445     Q_D(MailboxList);
0446     if (!MailboxList::parse(scursor, send, isCRLF)) {
0447         return false;
0448     }
0449 
0450     if (d->mailboxList.count() > 1) {
0451         KMIME_WARN << "multiple mailboxes in header allowing only a single one!"
0452                    << Qt::endl;
0453     }
0454     return true;
0455 }
0456 
0457 //-----</SingleMailbox>-------------------------
0458 
0459 //-----<AddressList>-------------------------
0460 
0461 //@cond PRIVATE
0462 kmime_mk_trivial_ctor_with_dptr(AddressList, Address)
0463 kmime_mk_dptr_ctor(AddressList, Address)
0464 //@endcond
0465 
0466 QByteArray AddressList::as7BitString(bool withHeaderType) const
0467 {
0468     const Q_D(AddressList);
0469     if (d->addressList.isEmpty()) {
0470       return {};
0471     }
0472 
0473     QByteArray rv;
0474     if (withHeaderType) {
0475         rv = typeIntro();
0476     }
0477     for (const Types::Address &addr : std::as_const(d->addressList)) {
0478         const auto mailBoxList = addr.mailboxList;
0479         for (const Types::Mailbox &mbox : mailBoxList) {
0480             rv += mbox.as7BitString(d->encCS);
0481             rv += ", ";
0482         }
0483     }
0484     rv.resize(rv.length() - 2);
0485     return rv;
0486 }
0487 
0488 void AddressList::fromUnicodeString(const QString &s, const QByteArray &b)
0489 {
0490     Q_D(AddressList);
0491     d->encCS = cachedCharset(b);
0492     from7BitString(encodeRFC2047Sentence(s, b));
0493 }
0494 
0495 QString AddressList::asUnicodeString() const
0496 {
0497     Q_D(const AddressList);
0498     QStringList rv;
0499     for (const Types::Address &addr : std::as_const(d->addressList)) {
0500         rv.reserve(rv.size() + addr.mailboxList.size());
0501         const auto mailboxList = addr.mailboxList;
0502         for (const Types::Mailbox &mbox : mailboxList) {
0503             rv.append(mbox.prettyAddress());
0504         }
0505     }
0506     return rv.join(QLatin1String(", "));
0507 }
0508 
0509 void AddressList::clear()
0510 {
0511     Q_D(AddressList);
0512     d->addressList.clear();
0513 }
0514 
0515 bool AddressList::isEmpty() const
0516 {
0517     return d_func()->addressList.isEmpty();
0518 }
0519 
0520 void AddressList::addAddress(const Types::Mailbox &mbox)
0521 {
0522     Q_D(AddressList);
0523     Types::Address addr;
0524     addr.mailboxList.append(mbox);
0525     d->addressList.append(addr);
0526 }
0527 
0528 void AddressList::addAddress(const QByteArray &address,
0529                              const QString &displayName)
0530 {
0531     Q_D(AddressList);
0532     Types::Address addr;
0533     Types::Mailbox mbox;
0534     if (stringToMailbox(address, displayName, mbox)) {
0535         addr.mailboxList.append(mbox);
0536         d->addressList.append(addr);
0537     }
0538 }
0539 
0540 QVector<QByteArray> AddressList::addresses() const
0541 {
0542     QVector<QByteArray> rv;
0543     const auto addressList = d_func()->addressList;
0544     for (const Types::Address &addr : addressList) {
0545         const auto mailboxList = addr.mailboxList;
0546         for (const Types::Mailbox &mbox : mailboxList) {
0547             rv.append(mbox.address());
0548         }
0549     }
0550     return rv;
0551 }
0552 
0553 QStringList AddressList::displayNames() const
0554 {
0555     Q_D(const AddressList);
0556     QStringList rv;
0557     for (const Types::Address &addr : std::as_const(d->addressList)) {
0558         const auto mailboxList = addr.mailboxList;
0559         for (const Types::Mailbox &mbox : mailboxList) {
0560             if (mbox.hasName()) {
0561                 rv.append(mbox.name());
0562             } else {
0563                 rv.append(QString::fromLatin1(mbox.address()));
0564             }
0565         }
0566     }
0567     return rv;
0568 }
0569 
0570 QString AddressList::displayString() const
0571 {
0572     // optimize for single entry and avoid creation of the QStringList in that case?
0573     return displayNames().join(QLatin1String(", "));
0574 }
0575 
0576 Types::Mailbox::List AddressList::mailboxes() const
0577 {
0578     Types::Mailbox::List rv;
0579     const auto addressList = d_func()->addressList;
0580     for (const Types::Address &addr : addressList) {
0581         const auto mailboxList = addr.mailboxList;
0582         for (const Types::Mailbox &mbox : mailboxList) {
0583             rv.append(mbox);
0584         }
0585     }
0586     return rv;
0587 }
0588 
0589 bool AddressList::parse(const char *&scursor, const char *const send,
0590                         bool isCRLF)
0591 {
0592     Q_D(AddressList);
0593     QVector<Types::Address> maybeAddressList;
0594     if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) {
0595         return false;
0596     }
0597 
0598     d->addressList = maybeAddressList;
0599     return true;
0600 }
0601 
0602 //-----</AddressList>-------------------------
0603 
0604 //-----<Token>-------------------------
0605 
0606 //@cond PRIVATE
0607 kmime_mk_trivial_ctor_with_dptr(Token, Structured)
0608 kmime_mk_dptr_ctor(Token, Structured)
0609 //@endcond
0610 
0611 QByteArray Token::as7BitString(bool withHeaderType) const
0612 {
0613     if (isEmpty()) {
0614       return {};
0615     }
0616     if (withHeaderType) {
0617         return typeIntro() + d_func()->token;
0618     }
0619     return d_func()->token;
0620 }
0621 
0622 void Token::clear()
0623 {
0624     Q_D(Token);
0625     d->token.clear();
0626 }
0627 
0628 bool Token::isEmpty() const
0629 {
0630     return d_func()->token.isEmpty();
0631 }
0632 
0633 QByteArray Token::token() const
0634 {
0635     return d_func()->token;
0636 }
0637 
0638 void Token::setToken(const QByteArray &t)
0639 {
0640     Q_D(Token);
0641     d->token = t;
0642 }
0643 
0644 bool Token::parse(const char *&scursor, const char *const send, bool isCRLF)
0645 {
0646     Q_D(Token);
0647     clear();
0648     eatCFWS(scursor, send, isCRLF);
0649     // must not be empty:
0650     if (scursor == send) {
0651         return false;
0652     }
0653 
0654     QPair<const char *, int> maybeToken;
0655     if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) {
0656         return false;
0657     }
0658     d->token = QByteArray(maybeToken.first, maybeToken.second);
0659 
0660     // complain if trailing garbage is found:
0661     eatCFWS(scursor, send, isCRLF);
0662     if (scursor != send) {
0663         KMIME_WARN << "trailing garbage after token in header allowing "
0664                    "only a single token!"
0665                    << Qt::endl;
0666     }
0667     return true;
0668 }
0669 
0670 //-----</Token>-------------------------
0671 
0672 //-----<PhraseList>-------------------------
0673 
0674 //@cond PRIVATE
0675 kmime_mk_trivial_ctor_with_dptr(PhraseList, Structured)
0676 //@endcond
0677 
0678 QByteArray PhraseList::as7BitString(bool withHeaderType) const
0679 {
0680     const Q_D(PhraseList);
0681     if (isEmpty()) {
0682       return {};
0683     }
0684 
0685     QByteArray rv;
0686     if (withHeaderType) {
0687         rv = typeIntro();
0688     }
0689 
0690     for (int i = 0; i < d->phraseList.count(); ++i) {
0691         // FIXME: only encode when needed, quote when needed, etc.
0692         rv += encodeRFC2047String(d->phraseList[i], d->encCS, false, false);
0693         if (i != d->phraseList.count() - 1) {
0694             rv += ", ";
0695         }
0696     }
0697 
0698     return rv;
0699 }
0700 
0701 QString PhraseList::asUnicodeString() const
0702 {
0703     return d_func()->phraseList.join(QLatin1String(", "));
0704 }
0705 
0706 void PhraseList::clear()
0707 {
0708     Q_D(PhraseList);
0709     d->phraseList.clear();
0710 }
0711 
0712 bool PhraseList::isEmpty() const
0713 {
0714     return d_func()->phraseList.isEmpty();
0715 }
0716 
0717 QStringList PhraseList::phrases() const
0718 {
0719     return d_func()->phraseList;
0720 }
0721 
0722 bool PhraseList::parse(const char *&scursor, const char *const send,
0723                        bool isCRLF)
0724 {
0725     Q_D(PhraseList);
0726     d->phraseList.clear();
0727 
0728     while (scursor != send) {
0729         eatCFWS(scursor, send, isCRLF);
0730         // empty entry ending the list: OK.
0731         if (scursor == send) {
0732             return true;
0733         }
0734         // empty entry: ignore.
0735         if (*scursor == ',') {
0736             scursor++;
0737             continue;
0738         }
0739 
0740         QString maybePhrase;
0741         if (!parsePhrase(scursor, send, maybePhrase, isCRLF)) {
0742             return false;
0743         }
0744         d->phraseList.append(maybePhrase);
0745 
0746         eatCFWS(scursor, send, isCRLF);
0747         // non-empty entry ending the list: OK.
0748         if (scursor == send) {
0749             return true;
0750         }
0751         // comma separating the phrases: eat.
0752         if (*scursor == ',') {
0753             scursor++;
0754         }
0755     }
0756     return true;
0757 }
0758 
0759 //-----</PhraseList>-------------------------
0760 
0761 //-----<DotAtom>-------------------------
0762 
0763 //@cond PRIVATE
0764 kmime_mk_trivial_ctor_with_dptr(DotAtom, Structured)
0765 //@endcond
0766 
0767 QByteArray DotAtom::as7BitString(bool withHeaderType) const
0768 {
0769     if (isEmpty()) {
0770       return {};
0771     }
0772 
0773     QByteArray rv;
0774     if (withHeaderType) {
0775         rv += typeIntro();
0776     }
0777 
0778     rv += d_func()->dotAtom;
0779     return rv;
0780 }
0781 
0782 QString DotAtom::asUnicodeString() const
0783 {
0784     return QString::fromLatin1(d_func()->dotAtom);
0785 }
0786 
0787 void DotAtom::clear()
0788 {
0789     Q_D(DotAtom);
0790     d->dotAtom.clear();
0791 }
0792 
0793 bool DotAtom::isEmpty() const
0794 {
0795     return d_func()->dotAtom.isEmpty();
0796 }
0797 
0798 bool DotAtom::parse(const char *&scursor, const char *const send,
0799                     bool isCRLF)
0800 {
0801     Q_D(DotAtom);
0802     QByteArray maybeDotAtom;
0803     if (!parseDotAtom(scursor, send, maybeDotAtom, isCRLF)) {
0804         return false;
0805     }
0806 
0807     d->dotAtom = maybeDotAtom;
0808 
0809     eatCFWS(scursor, send, isCRLF);
0810     if (scursor != send) {
0811         KMIME_WARN << "trailing garbage after dot-atom in header allowing "
0812                    "only a single dot-atom!"
0813                    << Qt::endl;
0814     }
0815     return true;
0816 }
0817 
0818 //-----</DotAtom>-------------------------
0819 
0820 //-----<Parametrized>-------------------------
0821 
0822 //@cond PRIVATE
0823 kmime_mk_trivial_ctor_with_dptr(Parametrized, Structured)
0824 kmime_mk_dptr_ctor(Parametrized, Structured)
0825 //@endcond
0826 
0827 QByteArray Parametrized::as7BitString(bool withHeaderType) const
0828 {
0829     const Q_D(Parametrized);
0830     if (isEmpty()) {
0831       return {};
0832     }
0833 
0834     QByteArray rv;
0835     if (withHeaderType) {
0836         rv += typeIntro();
0837     }
0838 
0839     bool first = true;
0840     for (QMap<QString, QString>::ConstIterator it = d->parameterHash.constBegin();
0841             it != d->parameterHash.constEnd(); ++it) {
0842         if (!first) {
0843             rv += "; ";
0844         } else {
0845             first = false;
0846         }
0847         if (isUsAscii(it.value())) {
0848             rv += it.key().toLatin1() + '=';
0849             QByteArray tmp = it.value().toLatin1();
0850             addQuotes(tmp, true);   // force quoting, eg. for whitespaces in parameter value
0851             rv += tmp;
0852         } else {
0853             if (useOutlookAttachmentEncoding()) {
0854                 rv += it.key().toLatin1() + '=';
0855                 qCDebug(KMIME_LOG) << "doing:" << it.value() << QLatin1String(d->encCS);
0856                 rv += "\"" + encodeRFC2047String(it.value(), d->encCS) + "\"";
0857             } else {
0858                 rv += it.key().toLatin1() + "*=";
0859                 rv += encodeRFC2231String(it.value(), d->encCS);
0860             }
0861         }
0862     }
0863 
0864     return rv;
0865 }
0866 
0867 QString Parametrized::parameter(const QString &key) const
0868 {
0869     return d_func()->parameterHash.value(key.toLower());
0870 }
0871 
0872 bool Parametrized::hasParameter(const QString &key) const
0873 {
0874     return d_func()->parameterHash.contains(key.toLower());
0875 }
0876 
0877 void Parametrized::setParameter(const QString &key, const QString &value)
0878 {
0879     Q_D(Parametrized);
0880     d->parameterHash.insert(key.toLower(), value);
0881 }
0882 
0883 bool Parametrized::isEmpty() const
0884 {
0885     return d_func()->parameterHash.isEmpty();
0886 }
0887 
0888 void Parametrized::clear()
0889 {
0890     Q_D(Parametrized);
0891     d->parameterHash.clear();
0892 }
0893 
0894 bool Parametrized::parse(const char  *&scursor, const char *const send,
0895                          bool isCRLF)
0896 {
0897     Q_D(Parametrized);
0898     d->parameterHash.clear();
0899     QByteArray charset;
0900     if (!parseParameterListWithCharset(scursor, send, d->parameterHash, charset, isCRLF)) {
0901         return false;
0902     }
0903     d->encCS = charset;
0904     return true;
0905 }
0906 
0907 //-----</Parametrized>-------------------------
0908 
0909 //-----<Ident>-------------------------
0910 
0911 //@cond PRIVATE
0912 kmime_mk_trivial_ctor_with_dptr(Ident, Address)
0913 kmime_mk_dptr_ctor(Ident, Address)
0914 //@endcond
0915 
0916 QByteArray Ident::as7BitString(bool withHeaderType) const
0917 {
0918     const Q_D(Ident);
0919     if (d->msgIdList.isEmpty()) {
0920       return {};
0921     }
0922 
0923     QByteArray rv;
0924     if (withHeaderType) {
0925         rv = typeIntro();
0926     }
0927     for (const Types::AddrSpec &addr : std::as_const(d->msgIdList)) {
0928         if (!addr.isEmpty()) {
0929             const QString asString = addr.asString();
0930             rv += '<';
0931             if (!asString.isEmpty()) {
0932                 rv += asString.toLatin1(); // FIXME: change parsing to use QByteArrays
0933             }
0934             rv += "> ";
0935         }
0936     }
0937     if (!rv.isEmpty()) {
0938         rv.resize(rv.length() - 1);
0939     }
0940     return rv;
0941 }
0942 
0943 void Ident::clear()
0944 {
0945     Q_D(Ident);
0946     d->msgIdList.clear();
0947     d->cachedIdentifier.clear();
0948 }
0949 
0950 bool Ident::isEmpty() const
0951 {
0952     return d_func()->msgIdList.isEmpty();
0953 }
0954 
0955 bool Ident::parse(const char *&scursor, const char *const send, bool isCRLF)
0956 {
0957     Q_D(Ident);
0958     // msg-id   := "<" id-left "@" id-right ">"
0959     // id-left  := dot-atom-text / no-fold-quote / local-part
0960     // id-right := dot-atom-text / no-fold-literal / domain
0961     //
0962     // equivalent to:
0963     // msg-id   := angle-addr
0964 
0965     d->msgIdList.clear();
0966     d->cachedIdentifier.clear();
0967 
0968     while (scursor != send) {
0969         eatCFWS(scursor, send, isCRLF);
0970         // empty entry ending the list: OK.
0971         if (scursor == send) {
0972             return true;
0973         }
0974         // empty entry: ignore.
0975         if (*scursor == ',') {
0976             scursor++;
0977             continue;
0978         }
0979 
0980         AddrSpec maybeMsgId;
0981         if (!parseAngleAddr(scursor, send, maybeMsgId, isCRLF)) {
0982             return false;
0983         }
0984         d->msgIdList.append(maybeMsgId);
0985 
0986         eatCFWS(scursor, send, isCRLF);
0987         // header end ending the list: OK.
0988         if (scursor == send) {
0989             return true;
0990         }
0991         // regular item separator: eat it.
0992         if (*scursor == ',') {
0993             scursor++;
0994         }
0995     }
0996     return true;
0997 }
0998 
0999 QVector<QByteArray> Ident::identifiers() const
1000 {
1001     QVector<QByteArray> rv;
1002     const auto msgIdList = d_func()->msgIdList;
1003     for (const Types::AddrSpec &addr : msgIdList) {
1004         if (!addr.isEmpty()) {
1005             const QString asString = addr.asString();
1006             if (!asString.isEmpty()) {
1007                 rv.append(asString.toLatin1());   // FIXME: change parsing to use QByteArrays
1008             }
1009         }
1010     }
1011     return rv;
1012 }
1013 
1014 void Ident::fromIdent(const Ident* ident)
1015 {
1016     d_func()->encCS = ident->d_func()->encCS;
1017     d_func()->msgIdList = ident->d_func()->msgIdList;
1018     d_func()->cachedIdentifier = ident->d_func()->cachedIdentifier;
1019 }
1020 
1021 void Ident::appendIdentifier(const QByteArray &id)
1022 {
1023     Q_D(Ident);
1024     QByteArray tmp = id;
1025     if (!tmp.startsWith('<')) {
1026         tmp.prepend('<');
1027     }
1028     if (!tmp.endsWith('>')) {
1029         tmp.append('>');
1030     }
1031     AddrSpec msgId;
1032     const char *cursor = tmp.constData();
1033     if (parseAngleAddr(cursor, cursor + tmp.length(), msgId)) {
1034         d->msgIdList.append(msgId);
1035     } else {
1036         qCWarning(KMIME_LOG) << "Unable to parse address spec!";
1037     }
1038 }
1039 
1040 //-----</Ident>-------------------------
1041 
1042 //-----<SingleIdent>-------------------------
1043 
1044 //@cond PRIVATE
1045 kmime_mk_trivial_ctor_with_dptr(SingleIdent, Ident)
1046 kmime_mk_dptr_ctor(SingleIdent, Ident)
1047 //@endcond
1048 
1049 QByteArray SingleIdent::identifier() const
1050 {
1051     if (d_func()->msgIdList.isEmpty()) {
1052       return {};
1053     }
1054 
1055     if (d_func()->cachedIdentifier.isEmpty()) {
1056         const Types::AddrSpec &addr = d_func()->msgIdList.first();
1057         if (!addr.isEmpty()) {
1058             const QString asString = addr.asString();
1059             if (!asString.isEmpty()) {
1060                 d_func()->cachedIdentifier = asString.toLatin1();// FIXME: change parsing to use QByteArrays
1061             }
1062         }
1063     }
1064 
1065     return d_func()->cachedIdentifier;
1066 }
1067 
1068 void SingleIdent::setIdentifier(const QByteArray &id)
1069 {
1070     Q_D(SingleIdent);
1071     d->msgIdList.clear();
1072     d->cachedIdentifier.clear();
1073     appendIdentifier(id);
1074 }
1075 
1076 bool SingleIdent::parse(const char *&scursor, const char *const send,
1077                         bool isCRLF)
1078 {
1079     Q_D(SingleIdent);
1080     if (!Ident::parse(scursor, send, isCRLF)) {
1081         return false;
1082     }
1083 
1084     if (d->msgIdList.count() > 1) {
1085         KMIME_WARN << "more than one msg-id in header "
1086                    << "allowing only a single one!"
1087                    << Qt::endl;
1088     }
1089     return true;
1090 }
1091 
1092 //-----</SingleIdent>-------------------------
1093 
1094 } // namespace Generics
1095 
1096 //-----<ReturnPath>-------------------------
1097 
1098 //@cond PRIVATE
1099 kmime_mk_trivial_ctor_with_name_and_dptr(ReturnPath, Generics::Address, Return-Path)
1100 //@endcond
1101 
1102 QByteArray ReturnPath::as7BitString(bool withHeaderType) const
1103 {
1104     if (isEmpty()) {
1105       return {};
1106     }
1107 
1108     QByteArray rv;
1109     if (withHeaderType) {
1110         rv += typeIntro();
1111     }
1112     rv += '<' + d_func()->mailbox.as7BitString(d_func()->encCS) + '>';
1113     return rv;
1114 }
1115 
1116 void ReturnPath::clear()
1117 {
1118     Q_D(ReturnPath);
1119     d->mailbox.setAddress(Types::AddrSpec());
1120     d->mailbox.setName(QString());
1121 }
1122 
1123 bool ReturnPath::isEmpty() const
1124 {
1125     const Q_D(ReturnPath);
1126     return !d->mailbox.hasAddress() && !d->mailbox.hasName();
1127 }
1128 
1129 bool ReturnPath::parse(const char *&scursor, const char *const send,
1130                        bool isCRLF)
1131 {
1132     Q_D(ReturnPath);
1133     eatCFWS(scursor, send, isCRLF);
1134     if (scursor == send) {
1135         return false;
1136     }
1137 
1138     const char *oldscursor = scursor;
1139 
1140     Mailbox maybeMailbox;
1141     if (!parseMailbox(scursor, send, maybeMailbox, isCRLF)) {
1142         // mailbox parsing failed, but check for empty brackets:
1143         scursor = oldscursor;
1144         if (*scursor != '<') {
1145             return false;
1146         }
1147         scursor++;
1148         eatCFWS(scursor, send, isCRLF);
1149         if (scursor == send || *scursor != '>') {
1150             return false;
1151         }
1152         scursor++;
1153 
1154         // prepare a Null mailbox:
1155         AddrSpec emptyAddrSpec;
1156         maybeMailbox.setName(QString());
1157         maybeMailbox.setAddress(emptyAddrSpec);
1158     } else {
1159         // check that there was no display-name:
1160         if (maybeMailbox.hasName()) {
1161             KMIME_WARN << "display-name \"" << maybeMailbox.name()
1162                        << "\" in Return-Path!" << Qt::endl;
1163         }
1164     }
1165     d->mailbox = maybeMailbox;
1166 
1167     // see if that was all:
1168     eatCFWS(scursor, send, isCRLF);
1169     // and warn if it wasn't:
1170     if (scursor != send) {
1171         KMIME_WARN << "trailing garbage after angle-addr in Return-Path!"
1172                    << Qt::endl;
1173     }
1174     return true;
1175 }
1176 
1177 //-----</ReturnPath>-------------------------
1178 
1179 //-----<Generic>-------------------------------
1180 
1181 // NOTE: Do *not* register Generic with HeaderFactory, since its type() is changeable.
1182 
1183 Generic::Generic() : Generics::Unstructured(new GenericPrivate)
1184 {
1185 }
1186 
1187 Generic::Generic(const char *t, int len) : Generics::Unstructured(new GenericPrivate)
1188 {
1189     setType(t, len);
1190 }
1191 
1192 Generic::~Generic()
1193 {
1194     Q_D(Generic);
1195     delete d;
1196     d_ptr = nullptr;
1197 }
1198 
1199 void Generic::clear()
1200 {
1201     Q_D(Generic);
1202     delete[] d->type;
1203     d->type = nullptr;
1204     Unstructured::clear();
1205 }
1206 
1207 bool Generic::isEmpty() const
1208 {
1209     return d_func()->type == nullptr || Unstructured::isEmpty();
1210 }
1211 
1212 const char *Generic::type() const
1213 {
1214     return d_func()->type;
1215 }
1216 
1217 void Generic::setType(const char *type, int len)
1218 {
1219     Q_D(Generic);
1220     if (d->type) {
1221         delete[] d->type;
1222     }
1223     if (type) {
1224         const int l = (len < 0 ? strlen(type) : len) + 1;
1225         d->type = new char[l];
1226         qstrncpy(d->type, type, l);
1227     } else {
1228         d->type = nullptr;
1229     }
1230 }
1231 
1232 //-----<Generic>-------------------------------
1233 
1234 //-----<MessageID>-----------------------------
1235 
1236 //@cond PRIVATE
1237 kmime_mk_trivial_ctor_with_name(MessageID, Generics::SingleIdent, Message-ID)
1238 //@endcond
1239 
1240 void MessageID::generate(const QByteArray &fqdn)
1241 {
1242     setIdentifier('<' + uniqueString() + '@' + fqdn + '>');
1243 }
1244 
1245 //-----</MessageID>----------------------------
1246 
1247 //-----<Control>-------------------------------
1248 
1249 //@cond PRIVATE
1250 kmime_mk_trivial_ctor_with_name_and_dptr(Control, Generics::Structured, Control)
1251 //@endcond
1252 
1253 QByteArray Control::as7BitString(bool withHeaderType) const
1254 {
1255     const Q_D(Control);
1256     if (isEmpty()) {
1257       return {};
1258     }
1259 
1260     QByteArray rv;
1261     if (withHeaderType) {
1262         rv += typeIntro();
1263     }
1264 
1265     rv += d->name;
1266     if (!d->parameter.isEmpty()) {
1267         rv += ' ' + d->parameter;
1268     }
1269     return rv;
1270 }
1271 
1272 void Control::clear()
1273 {
1274     Q_D(Control);
1275     d->name.clear();
1276     d->parameter.clear();
1277 }
1278 
1279 bool Control::isEmpty() const
1280 {
1281     return d_func()->name.isEmpty();
1282 }
1283 
1284 QByteArray Control::controlType() const
1285 {
1286     return d_func()->name;
1287 }
1288 
1289 QByteArray Control::parameter() const
1290 {
1291     return d_func()->parameter;
1292 }
1293 
1294 bool Control::isCancel() const
1295 {
1296     return d_func()->name.toLower() == "cancel";
1297 }
1298 
1299 void Control::setCancel(const QByteArray &msgid)
1300 {
1301     Q_D(Control);
1302     d->name = "cancel";
1303     d->parameter = msgid;
1304 }
1305 
1306 bool Control::parse(const char *&scursor, const char *const send, bool isCRLF)
1307 {
1308     Q_D(Control);
1309     clear();
1310     eatCFWS(scursor, send, isCRLF);
1311     if (scursor == send) {
1312         return false;
1313     }
1314     const char *start = scursor;
1315     while (scursor != send && !isspace(*scursor)) {
1316         ++scursor;
1317     }
1318     d->name = QByteArray(start, scursor - start);
1319     eatCFWS(scursor, send, isCRLF);
1320     d->parameter = QByteArray(scursor, send - scursor);
1321     return true;
1322 }
1323 
1324 //-----</Control>------------------------------
1325 
1326 //-----<MailCopiesTo>--------------------------
1327 
1328 //@cond PRIVATE
1329 kmime_mk_trivial_ctor_with_name_and_dptr(MailCopiesTo,
1330         Generics::AddressList, Mail-Copies-To)
1331 //@endcond
1332 
1333 QByteArray MailCopiesTo::as7BitString(bool withHeaderType) const
1334 {
1335     QByteArray rv;
1336     if (withHeaderType) {
1337         rv += typeIntro();
1338     }
1339     if (!AddressList::isEmpty()) {
1340         rv += AddressList::as7BitString(false);
1341     } else {
1342         if (d_func()->alwaysCopy) {
1343             rv += "poster";
1344         } else if (d_func()->neverCopy) {
1345             rv += "nobody";
1346         }
1347     }
1348     return rv;
1349 }
1350 
1351 QString MailCopiesTo::asUnicodeString() const
1352 {
1353     if (!AddressList::isEmpty()) {
1354         return AddressList::asUnicodeString();
1355     }
1356     if (d_func()->alwaysCopy) {
1357         return QStringLiteral("poster");
1358     }
1359     if (d_func()->neverCopy) {
1360         return QStringLiteral("nobody");
1361     }
1362     return {};
1363 }
1364 
1365 void MailCopiesTo::clear()
1366 {
1367     Q_D(MailCopiesTo);
1368     AddressList::clear();
1369     d->alwaysCopy = false;
1370     d->neverCopy = false;
1371 }
1372 
1373 bool MailCopiesTo::isEmpty() const
1374 {
1375     return AddressList::isEmpty() && !(d_func()->alwaysCopy || d_func()->neverCopy);
1376 }
1377 
1378 bool MailCopiesTo::alwaysCopy() const
1379 {
1380     return !AddressList::isEmpty() || d_func()->alwaysCopy;
1381 }
1382 
1383 void MailCopiesTo::setAlwaysCopy()
1384 {
1385     Q_D(MailCopiesTo);
1386     clear();
1387     d->alwaysCopy = true;
1388 }
1389 
1390 bool MailCopiesTo::neverCopy() const
1391 {
1392     return d_func()->neverCopy;
1393 }
1394 
1395 void MailCopiesTo::setNeverCopy()
1396 {
1397     Q_D(MailCopiesTo);
1398     clear();
1399     d->neverCopy = true;
1400 }
1401 
1402 bool MailCopiesTo::parse(const char  *&scursor, const char *const send,
1403                          bool isCRLF)
1404 {
1405     Q_D(MailCopiesTo);
1406     clear();
1407     if (send - scursor == 5) {
1408         if (qstrnicmp("never", scursor, 5) == 0) {
1409             d->neverCopy = true;
1410             return true;
1411         }
1412     }
1413     if (send - scursor == 6) {
1414         if (qstrnicmp("always", scursor, 6) == 0 || qstrnicmp("poster", scursor, 6) == 0) {
1415             d->alwaysCopy = true;
1416             return true;
1417         }
1418         if (qstrnicmp("nobody", scursor, 6) == 0) {
1419             d->neverCopy = true;
1420             return true;
1421         }
1422     }
1423     return AddressList::parse(scursor, send, isCRLF);
1424 }
1425 
1426 //-----</MailCopiesTo>-------------------------
1427 
1428 //-----<Date>----------------------------------
1429 
1430 //@cond PRIVATE
1431 kmime_mk_trivial_ctor_with_name_and_dptr(Date, Generics::Structured, Date)
1432 //@endcond
1433 
1434 QByteArray Date::as7BitString(bool withHeaderType) const
1435 {
1436     if (isEmpty()) {
1437       return {};
1438     }
1439 
1440     QByteArray rv;
1441     if (withHeaderType) {
1442         rv += typeIntro();
1443     }
1444     //QT5 fix port to QDateTime Qt::RFC2822Date is not enough we need to fix it. We need to use QLocale("C") + add "ddd, ";
1445     //rv += d_func()->dateTime.toString(  Qt::RFC2822Date ).toLatin1();
1446     rv += QLocale::c().toString(d_func()->dateTime, QStringLiteral("ddd, ")).toLatin1();
1447     rv += d_func()->dateTime.toString(Qt::RFC2822Date).toLatin1();
1448 
1449     return rv;
1450 }
1451 
1452 void Date::clear() {
1453     Q_D(Date);
1454     d->dateTime = QDateTime();
1455 }
1456 
1457 bool Date::isEmpty() const {
1458     return d_func()->dateTime.isNull() || !d_func()->dateTime.isValid();
1459 }
1460 
1461 QDateTime Date::dateTime() const {
1462     return d_func()->dateTime;
1463 }
1464 
1465 void Date::setDateTime(const QDateTime & dt) {
1466     Q_D(Date);
1467     d->dateTime = dt;
1468 }
1469 
1470 int Date::ageInDays() const {
1471     QDate today = QDate::currentDate();
1472     return dateTime().date().daysTo(today);
1473 }
1474 
1475 bool Date::parse(const char *&scursor, const char *const send, bool isCRLF) {
1476     Q_D(Date);
1477     return parseDateTime(scursor, send, d->dateTime, isCRLF);
1478 }
1479 
1480 //-----</Date>---------------------------------
1481 
1482 //-----<Newsgroups>----------------------------
1483 
1484 //@cond PRIVATE
1485 kmime_mk_trivial_ctor_with_name_and_dptr(Newsgroups, Generics::Structured, Newsgroups)
1486 kmime_mk_trivial_ctor_with_name(FollowUpTo, Newsgroups, Followup-To)
1487 //@endcond
1488 
1489 QByteArray Newsgroups::as7BitString(bool withHeaderType) const {
1490     const Q_D(Newsgroups);
1491     if (isEmpty()) {
1492       return {};
1493     }
1494 
1495     QByteArray rv;
1496     if (withHeaderType) {
1497         rv += typeIntro();
1498     }
1499 
1500     for (int i = 0; i < d->groups.count(); ++i) {
1501         rv += d->groups[ i ];
1502         if (i != d->groups.count() - 1) {
1503             rv += ',';
1504         }
1505     }
1506     return rv;
1507 }
1508 
1509 void Newsgroups::fromUnicodeString(const QString & s, const QByteArray & b) {
1510     Q_UNUSED(b)
1511     Q_D(Newsgroups);
1512     from7BitString(s.toUtf8());
1513     d->encCS = cachedCharset("UTF-8");
1514 }
1515 
1516 QString Newsgroups::asUnicodeString() const {
1517     return QString::fromUtf8(as7BitString(false));
1518 }
1519 
1520 void Newsgroups::clear() {
1521     Q_D(Newsgroups);
1522     d->groups.clear();
1523 }
1524 
1525 bool Newsgroups::isEmpty() const {
1526     return d_func()->groups.isEmpty();
1527 }
1528 
1529 QVector<QByteArray> Newsgroups::groups() const {
1530     return d_func()->groups;
1531 }
1532 
1533 void Newsgroups::setGroups(const QVector<QByteArray> &groups) {
1534     Q_D(Newsgroups);
1535     d->groups = groups;
1536 }
1537 
1538 bool Newsgroups::isCrossposted() const {
1539     return d_func()->groups.count() >= 2;
1540 }
1541 
1542 bool Newsgroups::parse(const char *&scursor, const char *const send, bool isCRLF) {
1543     Q_D(Newsgroups);
1544     clear();
1545     while (true) {
1546       eatCFWS(scursor, send, isCRLF);
1547       if (scursor != send && *scursor == ',') {
1548         ++scursor;
1549       }
1550       eatCFWS(scursor, send, isCRLF);
1551       if (scursor == send) {
1552         return true;
1553       }
1554       const char *start = scursor;
1555       while (scursor != send && !isspace(*scursor) && *scursor != ',') {
1556         ++scursor;
1557       }
1558       QByteArray group(start, scursor - start);
1559       d->groups.append(group);
1560     }
1561     return true;
1562 }
1563 
1564 //-----</Newsgroups>---------------------------
1565 
1566 //-----<Lines>---------------------------------
1567 
1568 //@cond PRIVATE
1569 kmime_mk_trivial_ctor_with_name_and_dptr(Lines, Generics::Structured, Lines)
1570 //@endcond
1571 
1572 QByteArray Lines::as7BitString(bool withHeaderType) const {
1573     if (isEmpty()) {
1574       return {};
1575     }
1576 
1577     QByteArray num;
1578     num.setNum(d_func()->lines);
1579 
1580     if (withHeaderType) {
1581         return typeIntro() + num;
1582     }
1583     return num;
1584 }
1585 
1586 QString Lines::asUnicodeString() const {
1587     if (isEmpty()) {
1588       return {};
1589     }
1590     return QString::number(d_func()->lines);
1591 }
1592 
1593 void Lines::clear() {
1594     Q_D(Lines);
1595     d->lines = -1;
1596 }
1597 
1598 bool Lines::isEmpty() const {
1599     return d_func()->lines == -1;
1600 }
1601 
1602 int Lines::numberOfLines() const {
1603     return d_func()->lines;
1604 }
1605 
1606 void Lines::setNumberOfLines(int lines) {
1607     Q_D(Lines);
1608     d->lines = lines;
1609 }
1610 
1611 bool Lines::parse(const char *&scursor, const char *const send, bool isCRLF) {
1612     Q_D(Lines);
1613     eatCFWS(scursor, send, isCRLF);
1614     if (parseDigits(scursor, send, d->lines)  == 0) {
1615         clear();
1616         return false;
1617     }
1618     return true;
1619 }
1620 
1621 //-----</Lines>--------------------------------
1622 
1623 //-----<Content-Type>--------------------------
1624 
1625 //@cond PRIVATE
1626 kmime_mk_trivial_ctor_with_name_and_dptr(ContentType, Generics::Parametrized,
1627             Content-Type)
1628 //@endcond
1629 
1630 bool ContentType::isEmpty() const {
1631     return d_func()->mimeType.isEmpty();
1632 }
1633 
1634 void ContentType::clear() {
1635     Q_D(ContentType);
1636     d->category = CCsingle;
1637     d->mimeType.clear();
1638     Parametrized::clear();
1639 }
1640 
1641 QByteArray ContentType::as7BitString(bool withHeaderType) const {
1642     if (isEmpty()) {
1643       return {};
1644     }
1645 
1646     QByteArray rv;
1647     if (withHeaderType) {
1648         rv += typeIntro();
1649     }
1650 
1651     rv += mimeType();
1652     if (!Parametrized::isEmpty()) {
1653         rv += "; " + Parametrized::as7BitString(false);
1654     }
1655 
1656     return rv;
1657 }
1658 
1659 QByteArray ContentType::mimeType() const {
1660     Q_D(const ContentType);
1661     return d->mimeType;
1662 }
1663 
1664 QByteArray ContentType::mediaType() const {
1665     Q_D(const ContentType);
1666     const int pos = d->mimeType.indexOf('/');
1667     if (pos < 0) {
1668         return d->mimeType;
1669     } else {
1670         return d->mimeType.left(pos);
1671     }
1672 }
1673 
1674 QByteArray ContentType::subType() const {
1675     Q_D(const ContentType);
1676     const int pos = d->mimeType.indexOf('/');
1677     if (pos < 0) {
1678       return {};
1679     } else {
1680         return d->mimeType.mid(pos + 1);
1681     }
1682 }
1683 
1684 void ContentType::setMimeType(const QByteArray & mimeType) {
1685     Q_D(ContentType);
1686     d->mimeType = mimeType;
1687 
1688     if (isMultipart()) {
1689         d->category = CCcontainer;
1690     } else {
1691         d->category = CCsingle;
1692     }
1693 }
1694 
1695 bool ContentType::isMediatype(const char *mediatype) const {
1696     Q_D(const ContentType);
1697     const int len = strlen(mediatype);
1698     return qstrnicmp(d->mimeType.constData(), mediatype, len) == 0 &&
1699             (d->mimeType.at(len) == '/' || d->mimeType.size() == len);
1700 }
1701 
1702 bool ContentType::isSubtype(const char *subtype) const {
1703     Q_D(const ContentType);
1704     const int pos = d->mimeType.indexOf('/');
1705     if (pos < 0) {
1706         return false;
1707     }
1708     const int len = strlen(subtype);
1709     return qstrnicmp(d->mimeType.constData() + pos + 1, subtype, len) == 0 &&
1710             d->mimeType.size() == pos + len + 1;
1711 }
1712 
1713 bool ContentType::isMimeType(const char* mimeType) const
1714 {
1715     Q_D(const ContentType);
1716     return qstricmp(d->mimeType.constData(), mimeType) == 0;
1717 }
1718 
1719 bool ContentType::isText() const {
1720     return (isMediatype("text") || isEmpty());
1721 }
1722 
1723 bool ContentType::isPlainText() const {
1724     return (qstricmp(d_func()->mimeType.constData(), "text/plain") == 0 || isEmpty());
1725 }
1726 
1727 bool ContentType::isHTMLText() const {
1728     return qstricmp(d_func()->mimeType.constData(), "text/html") == 0;
1729 }
1730 
1731 bool ContentType::isImage() const {
1732     return isMediatype("image");
1733 }
1734 
1735 bool ContentType::isMultipart() const {
1736     return isMediatype("multipart");
1737 }
1738 
1739 bool ContentType::isPartial() const {
1740     return qstricmp(d_func()->mimeType.constData(), "message/partial") == 0;
1741 }
1742 
1743 QByteArray ContentType::charset() const {
1744     QByteArray ret = parameter(QStringLiteral("charset")).toLatin1();
1745     if (ret.isEmpty()) {
1746         //return the default-charset if necessary
1747         ret = Content::defaultCharset();
1748     }
1749     return ret;
1750 }
1751 
1752 void ContentType::setCharset(const QByteArray & s) {
1753     setParameter(QStringLiteral("charset"), QString::fromLatin1(s));
1754 }
1755 
1756 QByteArray ContentType::boundary() const {
1757     return parameter(QStringLiteral("boundary")).toLatin1();
1758 }
1759 
1760 void ContentType::setBoundary(const QByteArray & s) {
1761     setParameter(QStringLiteral("boundary"), QString::fromLatin1(s));
1762 }
1763 
1764 QString ContentType::name() const {
1765     return parameter(QStringLiteral("name"));
1766 }
1767 
1768 void ContentType::setName(const QString & s, const QByteArray & cs) {
1769     Q_D(ContentType);
1770     d->encCS = cs;
1771     setParameter(QStringLiteral("name"), s);
1772 }
1773 
1774 QByteArray ContentType::id() const {
1775     return parameter(QStringLiteral("id")).toLatin1();
1776 }
1777 
1778 void ContentType::setId(const QByteArray & s) {
1779     setParameter(QStringLiteral("id"), QString::fromLatin1(s));
1780 }
1781 
1782 int ContentType::partialNumber() const {
1783     QByteArray p = parameter(QStringLiteral("number")).toLatin1();
1784     if (!p.isEmpty()) {
1785         return p.toInt();
1786     } else {
1787         return -1;
1788     }
1789 }
1790 
1791 int ContentType::partialCount() const {
1792     QByteArray p = parameter(QStringLiteral("total")).toLatin1();
1793     if (!p.isEmpty()) {
1794         return p.toInt();
1795     } else {
1796         return -1;
1797     }
1798 }
1799 
1800 contentCategory ContentType::category() const {
1801     return d_func()->category;
1802 }
1803 
1804 void ContentType::setCategory(contentCategory c) {
1805     Q_D(ContentType);
1806     d->category = c;
1807 }
1808 
1809 void ContentType::setPartialParams(int total, int number) {
1810     setParameter(QStringLiteral("number"), QString::number(number));
1811     setParameter(QStringLiteral("total"), QString::number(total));
1812 }
1813 
1814 bool ContentType::parse(const char *&scursor, const char *const send,
1815                         bool isCRLF) {
1816     Q_D(ContentType);
1817     // content-type: type "/" subtype *(";" parameter)
1818     clear();
1819     eatCFWS(scursor, send, isCRLF);
1820     if (scursor == send) {
1821         return false; // empty header
1822     }
1823 
1824     // type
1825     QPair<const char *, int> maybeMimeType;
1826     if (!parseToken(scursor, send, maybeMimeType, ParseTokenNoFlag)) {
1827         return false;
1828     }
1829     // subtype
1830     eatCFWS(scursor, send, isCRLF);
1831     if (scursor == send || *scursor != '/') {
1832         return false;
1833     }
1834     scursor++;
1835     eatCFWS(scursor, send, isCRLF);
1836     if (scursor == send) {
1837         return false;
1838     }
1839     QPair<const char *, int> maybeSubType;
1840     if (!parseToken(scursor, send, maybeSubType, ParseTokenNoFlag)) {
1841         return false;
1842     }
1843 
1844     d->mimeType.reserve(maybeMimeType.second + maybeSubType.second + 1);
1845     d->mimeType = QByteArray(maybeMimeType.first, maybeMimeType.second).toLower()
1846                     + '/' + QByteArray(maybeSubType.first, maybeSubType.second).toLower();
1847 
1848     // parameter list
1849     eatCFWS(scursor, send, isCRLF);
1850     if (scursor == send) {
1851         goto success; // no parameters
1852     }
1853     if (*scursor != ';') {
1854         return false;
1855     }
1856     scursor++;
1857 
1858     if (!Parametrized::parse(scursor, send, isCRLF)) {
1859         return false;
1860     }
1861 
1862     // adjust category
1863 success:
1864     if (isMultipart()) {
1865         d->category = CCcontainer;
1866     } else {
1867         d->category = CCsingle;
1868     }
1869     return true;
1870 }
1871 
1872 //-----</Content-Type>-------------------------
1873 
1874 //-----<ContentID>----------------------
1875 
1876 kmime_mk_trivial_ctor_with_name_and_dptr(ContentID, SingleIdent, Content-ID)
1877 kmime_mk_dptr_ctor(ContentID, SingleIdent)
1878 
1879 bool ContentID::parse(const char *&scursor, const char *const send, bool isCRLF) {
1880     Q_D(ContentID);
1881     // Content-id := "<" contentid ">"
1882     // contentid := now whitespaces
1883 
1884     const char *origscursor = scursor;
1885     if (!SingleIdent::parse(scursor, send, isCRLF)) {
1886         scursor = origscursor;
1887         d->msgIdList.clear();
1888         d->cachedIdentifier.clear();
1889 
1890         while (scursor != send) {
1891             eatCFWS(scursor, send, isCRLF);
1892             // empty entry ending the list: OK.
1893             if (scursor == send) {
1894                 return true;
1895             }
1896             // empty entry: ignore.
1897             if (*scursor == ',') {
1898                 scursor++;
1899                 continue;
1900             }
1901 
1902             AddrSpec maybeContentId;
1903             // Almost parseAngleAddr
1904             if (scursor == send || *scursor != '<') {
1905                 return false;
1906             }
1907             scursor++; // eat '<'
1908 
1909             eatCFWS(scursor, send, isCRLF);
1910             if (scursor == send) {
1911                 return false;
1912             }
1913 
1914             // Save chars until '>''
1915             QByteArray result;
1916             if (!parseDotAtom(scursor, send, result, false)) {
1917                 return false;
1918             }
1919 
1920             eatCFWS(scursor, send, isCRLF);
1921             if (scursor == send || *scursor != '>') {
1922                 return false;
1923             }
1924             scursor++;
1925             // /Almost parseAngleAddr
1926 
1927             maybeContentId.localPart = QString::fromLatin1(result); // FIXME: just use QByteArray instead of AddrSpec in msgIdList?
1928             d->msgIdList.append(maybeContentId);
1929 
1930             eatCFWS(scursor, send, isCRLF);
1931             // header end ending the list: OK.
1932             if (scursor == send) {
1933                 return true;
1934             }
1935             // regular item separator: eat it.
1936             if (*scursor == ',') {
1937                 scursor++;
1938             }
1939         }
1940         return true;
1941     } else {
1942         return true;
1943     }
1944 }
1945 
1946 //-----</ContentID>----------------------
1947 
1948 //-----<ContentTransferEncoding>----------------------------
1949 
1950 //@cond PRIVATE
1951 kmime_mk_trivial_ctor_with_name_and_dptr(ContentTransferEncoding,
1952         Generics::Token, Content-Transfer-Encoding)
1953 //@endcond
1954 
1955 typedef struct {
1956     const char *s;
1957     int e;
1958 } encTableType;
1959 
1960 static const encTableType encTable[] = {
1961     { "7Bit", CE7Bit },
1962     { "8Bit", CE8Bit },
1963     { "quoted-printable", CEquPr },
1964     { "base64", CEbase64 },
1965     { "x-uuencode", CEuuenc },
1966     { "binary", CEbinary },
1967     { nullptr, 0}
1968 };
1969 
1970 void ContentTransferEncoding::clear() {
1971     Q_D(ContentTransferEncoding);
1972     d->decoded = true;
1973     d->cte = CE7Bit;
1974     Token::clear();
1975 }
1976 
1977 contentEncoding ContentTransferEncoding::encoding() const {
1978     return d_func()->cte;
1979 }
1980 
1981 void ContentTransferEncoding::setEncoding(contentEncoding e) {
1982     Q_D(ContentTransferEncoding);
1983     d->cte = e;
1984 
1985     for (int i = 0; encTable[i].s != nullptr; ++i) {
1986         if (d->cte == encTable[i].e) {
1987             setToken(encTable[i].s);
1988             break;
1989         }
1990     }
1991 }
1992 
1993 bool ContentTransferEncoding::isDecoded() const {
1994     return d_func()->decoded;
1995 }
1996 
1997 void ContentTransferEncoding::setDecoded(bool decoded) {
1998     Q_D(ContentTransferEncoding);
1999     d->decoded = decoded;
2000 }
2001 
2002 bool ContentTransferEncoding::needToEncode() const {
2003     const Q_D(ContentTransferEncoding);
2004     return d->decoded && (d->cte == CEquPr || d->cte == CEbase64);
2005 }
2006 
2007 bool ContentTransferEncoding::parse(const char  *&scursor,
2008                                     const char *const send, bool isCRLF) {
2009     Q_D(ContentTransferEncoding);
2010     clear();
2011     if (!Token::parse(scursor, send, isCRLF)) {
2012         return false;
2013     }
2014 
2015     // TODO: error handling in case of an unknown encoding?
2016     for (int i = 0; encTable[i].s != nullptr; ++i) {
2017         if (qstricmp(token().constData(), encTable[i].s) == 0) {
2018             d->cte = (contentEncoding)encTable[i].e;
2019             break;
2020         }
2021     }
2022     d->decoded = (d->cte == CE7Bit || d->cte == CE8Bit);
2023     return true;
2024 }
2025 
2026 //-----</ContentTransferEncoding>---------------------------
2027 
2028 //-----<ContentDisposition>--------------------------
2029 
2030 //@cond PRIVATE
2031 kmime_mk_trivial_ctor_with_name_and_dptr(ContentDisposition,
2032         Generics::Parametrized, Content-Disposition)
2033 //@endcond
2034 
2035 QByteArray ContentDisposition::as7BitString(bool withHeaderType) const {
2036     if (isEmpty()) {
2037       return {};
2038     }
2039 
2040     QByteArray rv;
2041     if (withHeaderType) {
2042         rv += typeIntro();
2043     }
2044 
2045     if (d_func()->disposition == CDattachment) {
2046         rv += "attachment";
2047     } else if (d_func()->disposition == CDinline) {
2048         rv += "inline";
2049     } else {
2050       return {};
2051     }
2052 
2053     if (!Parametrized::isEmpty()) {
2054         rv += "; " + Parametrized::as7BitString(false);
2055     }
2056 
2057     return rv;
2058 }
2059 
2060 bool ContentDisposition::isEmpty() const {
2061     return d_func()->disposition == CDInvalid;
2062 }
2063 
2064 void ContentDisposition::clear() {
2065     Q_D(ContentDisposition);
2066     d->disposition = CDInvalid;
2067     Parametrized::clear();
2068 }
2069 
2070 contentDisposition ContentDisposition::disposition() const {
2071     return d_func()->disposition;
2072 }
2073 
2074 void ContentDisposition::setDisposition(contentDisposition disp) {
2075     Q_D(ContentDisposition);
2076     d->disposition = disp;
2077 }
2078 
2079 QString KMime::Headers::ContentDisposition::filename() const {
2080     return parameter(QStringLiteral("filename"));
2081 }
2082 
2083 void ContentDisposition::setFilename(const QString & filename) {
2084     setParameter(QStringLiteral("filename"), filename);
2085 }
2086 
2087 bool ContentDisposition::parse(const char  *&scursor, const char *const send,
2088                                 bool isCRLF) {
2089     Q_D(ContentDisposition);
2090     clear();
2091 
2092     // token
2093     QByteArray token;
2094     eatCFWS(scursor, send, isCRLF);
2095     if (scursor == send) {
2096         return false;
2097     }
2098 
2099     QPair<const char *, int> maybeToken;
2100     if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) {
2101         return false;
2102     }
2103 
2104     token = QByteArray(maybeToken.first, maybeToken.second).toLower();
2105     if (token == "inline") {
2106         d->disposition = CDinline;
2107     } else if (token == "attachment") {
2108         d->disposition = CDattachment;
2109     } else {
2110         return false;
2111     }
2112 
2113     // parameter list
2114     eatCFWS(scursor, send, isCRLF);
2115     if (scursor == send) {
2116         return true; // no parameters
2117     }
2118 
2119     if (*scursor != ';') {
2120         return false;
2121     }
2122     scursor++;
2123 
2124     return Parametrized::parse(scursor, send, isCRLF);
2125 }
2126 
2127 //-----</ContentDisposition>-------------------------
2128 
2129 //@cond PRIVATE
2130 kmime_mk_trivial_ctor_with_name(Subject, Generics::Unstructured, Subject)
2131 //@endcond
2132 
2133 Base *createHeader(const QByteArray & type) {
2134     return HeaderFactory::createHeader(type);
2135 }
2136 
2137 //@cond PRIVATE
2138 kmime_mk_trivial_ctor_with_name(ContentDescription,
2139                                 Generics::Unstructured, Content-Description)
2140 kmime_mk_trivial_ctor_with_name(ContentLocation,
2141                                 Generics::Unstructured, Content-Location)
2142 kmime_mk_trivial_ctor_with_name(From, Generics::MailboxList, From)
2143 kmime_mk_trivial_ctor_with_name(Sender, Generics::SingleMailbox, Sender)
2144 kmime_mk_trivial_ctor_with_name(To, Generics::AddressList, To)
2145 kmime_mk_trivial_ctor_with_name(Cc, Generics::AddressList, Cc)
2146 kmime_mk_trivial_ctor_with_name(Bcc, Generics::AddressList, Bcc)
2147 kmime_mk_trivial_ctor_with_name(ReplyTo, Generics::AddressList, Reply-To)
2148 kmime_mk_trivial_ctor_with_name(Keywords, Generics::PhraseList, Keywords)
2149 kmime_mk_trivial_ctor_with_name(MIMEVersion, Generics::DotAtom, MIME-Version)
2150 kmime_mk_trivial_ctor_with_name(Supersedes, Generics::SingleIdent, Supersedes)
2151 kmime_mk_trivial_ctor_with_name(InReplyTo, Generics::Ident, In-Reply-To)
2152 kmime_mk_trivial_ctor_with_name(References, Generics::Ident, References)
2153 kmime_mk_trivial_ctor_with_name(Organization, Generics::Unstructured, Organization)
2154 kmime_mk_trivial_ctor_with_name(UserAgent, Generics::Unstructured, User-Agent)
2155 //@endcond
2156 
2157 } // namespace Headers
2158 
2159 } // namespace KMime