File indexing completed on 2022-11-23 12:03:48

0001 /*  -*- c++ -*-
0002     kmime_header_types.cpp
0003 
0004     KMime, the KDE Internet mail/usenet news message library.
0005     SPDX-FileCopyrightText: 2001-2002 Marc Mutz <mutz@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kmime_types.h"
0011 #include "kmime_codecs.h"
0012 #include "kmime_header_parsing.h"
0013 #include "kmime_header_parsing_p.h"
0014 #include "kmime_util.h"
0015 #include "kmime_util_p.h"
0016 #include "kmime_debug.h"
0017 
0018 #include <KCodecs>
0019 
0020 #include <QStringList>
0021 #include <QUrl>
0022 
0023 namespace KMime
0024 {
0025 
0026 namespace Types
0027 {
0028 
0029 // QUrl::fromAce is extremely expensive, so only use it when necessary.
0030 // Fortunately, the presence of IDNA is readily detected with a substring match...
0031 static inline QString QUrl_fromAce_wrapper(const QString &domain)
0032 {
0033     if (domain.contains(QLatin1String("xn--"))) {
0034         return QUrl::fromAce(domain.toLatin1());
0035     } else {
0036         return domain;
0037     }
0038 }
0039 
0040 static QString addr_spec_as_string(const AddrSpec &as, bool pretty)
0041 {
0042     if (as.isEmpty()) {
0043       return {};
0044     }
0045 
0046     static const QChar dotChar = QLatin1Char('.');
0047     static const QChar backslashChar = QLatin1Char('\\');
0048     static const QChar quoteChar = QLatin1Char('"');
0049 
0050     bool needsQuotes = false;
0051     QString result;
0052     result.reserve(as.localPart.length() + as.domain.length() + 1);
0053     for (int i = 0 ; i < as.localPart.length() ; ++i) {
0054         const QChar ch = as.localPart.at(i);
0055         if (ch == dotChar || isAText(ch.toLatin1())) {
0056             result += ch;
0057         } else {
0058             needsQuotes = true;
0059             if (ch == backslashChar || ch == quoteChar) {
0060                 result += backslashChar;
0061             }
0062             result += ch;
0063         }
0064     }
0065     const QString dom = pretty ? QUrl_fromAce_wrapper(as.domain) : as.domain ;
0066     if (needsQuotes) {
0067         result = quoteChar + result + quoteChar;
0068     }
0069     if (dom.isEmpty()) {
0070         return result;
0071     } else {
0072         result += QLatin1Char('@');
0073         result += dom;
0074         return result;
0075     }
0076 }
0077 
0078 QString AddrSpec::asString() const
0079 {
0080     return addr_spec_as_string(*this, false);
0081 }
0082 
0083 QString AddrSpec::asPrettyString() const
0084 {
0085     return addr_spec_as_string(*this, true);
0086 }
0087 
0088 bool AddrSpec::isEmpty() const
0089 {
0090     return localPart.isEmpty() && domain.isEmpty();
0091 }
0092 
0093 QByteArray Mailbox::address() const
0094 {
0095     QByteArray result;
0096     const QString asString = addr_spec_as_string(mAddrSpec, false);
0097     if (!asString.isEmpty()) {
0098         result = asString.toLatin1();
0099     }
0100     return result;
0101     //return mAddrSpec.asString().toLatin1();
0102 }
0103 
0104 AddrSpec Mailbox::addrSpec() const
0105 {
0106     return mAddrSpec;
0107 }
0108 
0109 QString Mailbox::name() const
0110 {
0111     return mDisplayName;
0112 }
0113 
0114 void Mailbox::setAddress(const AddrSpec &addr)
0115 {
0116     mAddrSpec = addr;
0117 }
0118 
0119 void Mailbox::setAddress(const QByteArray &addr)
0120 {
0121     const char *cursor = addr.constData();
0122     if (!HeaderParsing::parseAngleAddr(cursor,
0123                                        cursor + addr.length(), mAddrSpec)) {
0124         if (!HeaderParsing::parseAddrSpec(cursor, cursor + addr.length(),
0125                                           mAddrSpec)) {
0126             qCWarning(KMIME_LOG) << "Mailbox: Invalid address";
0127             return;
0128         }
0129     }
0130 }
0131 
0132 void Mailbox::setName(const QString &name)
0133 {
0134     mDisplayName = removeBidiControlChars(name);
0135 }
0136 
0137 void Mailbox::setNameFrom7Bit(const QByteArray &name,
0138                               const QByteArray &defaultCharset)
0139 {
0140     QByteArray cs;
0141     setName(KCodecs::decodeRFC2047String(name, &cs, defaultCharset));
0142 }
0143 
0144 bool Mailbox::hasAddress() const
0145 {
0146     return !mAddrSpec.isEmpty();
0147 }
0148 
0149 bool Mailbox::hasName() const
0150 {
0151     return !mDisplayName.isEmpty();
0152 }
0153 
0154 QString Mailbox::prettyAddress(Quoting quoting) const
0155 {
0156     if (!hasName()) {
0157         return QLatin1String(address());
0158     }
0159     QString s = name();
0160     if (quoting != QuoteNever) {
0161         addQuotes(s, quoting == QuoteAlways /*bool force*/);
0162     }
0163 
0164     if (hasAddress()) {
0165         s += QLatin1String(" <") + QLatin1String(address()) + QLatin1Char('>');
0166     }
0167     return s;
0168 }
0169 
0170 void Mailbox::fromUnicodeString(const QString &s)
0171 {
0172     from7BitString(encodeRFC2047Sentence(s, "utf-8"));
0173 }
0174 
0175 void Mailbox::from7BitString(const QByteArray &s)
0176 {
0177     const char *cursor = s.constData();
0178     HeaderParsing::parseMailbox(cursor, cursor + s.length(), *this);
0179 }
0180 
0181 QByteArray Mailbox::as7BitString(const QByteArray &encCharset) const
0182 {
0183     if (!hasName()) {
0184         return address();
0185     }
0186     QByteArray rv;
0187     if (isUsAscii(name())) {
0188         QByteArray tmp = name().toLatin1();
0189         addQuotes(tmp, false);
0190         rv += tmp;
0191     } else {
0192         rv += encodeRFC2047String(name(), encCharset, true);
0193     }
0194     if (hasAddress()) {
0195         rv += " <" + address() + '>';
0196     }
0197     return rv;
0198 }
0199 
0200 QVector<KMime::Types::Mailbox> Mailbox::listFromUnicodeString(const QString &s)
0201 {
0202     return listFrom7BitString(encodeRFC2047Sentence(s, "utf-8"));
0203 }
0204 
0205 QVector<KMime::Types::Mailbox> Mailbox::listFrom7BitString(const QByteArray& s)
0206 {
0207     QVector<KMime::Types::Mailbox> res;
0208     QVector<KMime::Types::Address> maybeAddressList;
0209     const char *scursor = s.constData();
0210     if (!HeaderParsing::parseAddressList(scursor, scursor + s.size(), maybeAddressList)) {
0211         return res;
0212     }
0213 
0214     res.reserve(maybeAddressList.size());
0215     for (const auto &it : std::as_const(maybeAddressList)) {
0216         res += (it).mailboxList;
0217     }
0218     return res;
0219 }
0220 
0221 QString Mailbox::listToUnicodeString(const QVector<Mailbox>& mailboxes)
0222 {
0223     if (mailboxes.size() == 1) { // QStringList free fast-path for the common case
0224         return mailboxes.at(0).prettyAddress();
0225     }
0226 
0227     QStringList rv;
0228     rv.reserve(mailboxes.count());
0229     for (const Types::Mailbox &mbox : mailboxes) {
0230         rv.append(mbox.prettyAddress());
0231     }
0232     return rv.join(QLatin1String(", "));
0233 }
0234 
0235 } // namespace Types
0236 
0237 } // namespace KMime