File indexing completed on 2023-10-03 04:47:28

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