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

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_p.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(QLatin1StringView("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 QLatin1StringView(address());
0158     }
0159     QString s = name();
0160     if (quoting != QuoteNever) {
0161         addQuotes(s, quoting == QuoteAlways /*bool force*/);
0162     }
0163 
0164     if (hasAddress()) {
0165       s +=
0166           QLatin1StringView(" <") + QLatin1StringView(address()) + QLatin1Char('>');
0167     }
0168     return s;
0169 }
0170 
0171 void Mailbox::fromUnicodeString(const QString &s)
0172 {
0173     from7BitString(encodeRFC2047Sentence(s, "utf-8"));
0174 }
0175 
0176 void Mailbox::from7BitString(const QByteArray &s)
0177 {
0178     const char *cursor = s.constData();
0179     HeaderParsing::parseMailbox(cursor, cursor + s.length(), *this);
0180 }
0181 
0182 QByteArray Mailbox::as7BitString(const QByteArray &encCharset) const
0183 {
0184     if (!hasName()) {
0185         return address();
0186     }
0187     QByteArray rv;
0188     if (isUsAscii(name())) {
0189         QByteArray tmp = name().toLatin1();
0190         addQuotes(tmp, false);
0191         rv += tmp;
0192     } else {
0193         rv += encodeRFC2047String(name(), encCharset, true);
0194     }
0195     if (hasAddress()) {
0196         rv += " <" + address() + '>';
0197     }
0198     return rv;
0199 }
0200 
0201 QList<KMime::Types::Mailbox> Mailbox::listFromUnicodeString(const QString &s) {
0202     return listFrom7BitString(encodeRFC2047Sentence(s, "utf-8"));
0203 }
0204 
0205 QList<KMime::Types::Mailbox> Mailbox::listFrom7BitString(const QByteArray &s) {
0206     QList<KMime::Types::Mailbox> res;
0207     QList<KMime::Types::Address> maybeAddressList;
0208     const char *scursor = s.constData();
0209     if (!HeaderParsing::parseAddressList(scursor, scursor + s.size(), maybeAddressList)) {
0210         return res;
0211     }
0212 
0213     res.reserve(maybeAddressList.size());
0214     for (const auto &it : std::as_const(maybeAddressList)) {
0215         res += (it).mailboxList;
0216     }
0217     return res;
0218 }
0219 
0220 QString Mailbox::listToUnicodeString(const QList<Mailbox> &mailboxes) {
0221     if (mailboxes.size() == 1) { // QStringList free fast-path for the common case
0222         return mailboxes.at(0).prettyAddress();
0223     }
0224 
0225     QStringList rv;
0226     rv.reserve(mailboxes.count());
0227     for (const Types::Mailbox &mbox : mailboxes) {
0228         rv.append(mbox.prettyAddress());
0229     }
0230     return rv.join(QLatin1StringView(", "));
0231 }
0232 
0233 } // namespace Types
0234 
0235 } // namespace KMime