File indexing completed on 2024-04-21 16:06:18

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