File indexing completed on 2024-04-28 09:12:11

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(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 QList<KMime::Types::Mailbox> Mailbox::listFromUnicodeString(const QString &s) {
0201     return listFrom7BitString(encodeRFC2047Sentence(s, "utf-8"));
0202 }
0203 
0204 QList<KMime::Types::Mailbox> Mailbox::listFrom7BitString(const QByteArray &s) {
0205     QList<KMime::Types::Mailbox> res;
0206     QList<KMime::Types::Address> maybeAddressList;
0207     const char *scursor = s.constData();
0208     if (!HeaderParsing::parseAddressList(scursor, scursor + s.size(), maybeAddressList)) {
0209         return res;
0210     }
0211 
0212     res.reserve(maybeAddressList.size());
0213     for (const auto &it : std::as_const(maybeAddressList)) {
0214         res += (it).mailboxList;
0215     }
0216     return res;
0217 }
0218 
0219 QString Mailbox::listToUnicodeString(const QList<Mailbox> &mailboxes) {
0220     if (mailboxes.size() == 1) { // QStringList free fast-path for the common case
0221         return mailboxes.at(0).prettyAddress();
0222     }
0223 
0224     QStringList rv;
0225     rv.reserve(mailboxes.count());
0226     for (const Types::Mailbox &mbox : mailboxes) {
0227         rv.append(mbox.prettyAddress());
0228     }
0229     return rv.join(QLatin1String(", "));
0230 }
0231 
0232 } // namespace Types
0233 
0234 } // namespace KMime