File indexing completed on 2024-04-21 14:53:45

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
0005   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0006   SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com>
0007   SPDX-FileCopyrightText: 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0008 
0009   SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 /**
0012   @file
0013   This file is part of the API for handling calendar data and
0014   defines the Person class.
0015 
0016   @brief
0017   Represents a person, by name and email address.
0018 
0019   @author Cornelius Schumacher \<schumacher@kde.org\>
0020   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
0021 */
0022 
0023 #include "person.h"
0024 #include "person_p.h"
0025 
0026 #include <QDataStream>
0027 #include <QRegularExpression>
0028 
0029 using namespace KCalendarCore;
0030 
0031 /**
0032   Private class that helps to provide binary compatibility between releases.
0033   @internal
0034 */
0035 //@cond PRIVATE
0036 class Q_DECL_HIDDEN KCalendarCore::Person::Private : public QSharedData
0037 {
0038 public:
0039     QString mName; // person name
0040     QString mEmail; // person email address
0041 };
0042 //@endcond
0043 
0044 Person::Person()
0045     : d(new KCalendarCore::Person::Private)
0046 {
0047 }
0048 
0049 Person::Person(const QString &name, const QString &email)
0050     : d(new KCalendarCore::Person::Private)
0051 {
0052     d->mName = name;
0053     d->mEmail = email;
0054 }
0055 
0056 Person::Person(const Person &person)
0057     : d(person.d)
0058 {
0059 }
0060 
0061 Person::~Person() = default;
0062 
0063 bool KCalendarCore::Person::operator==(const Person &person) const
0064 {
0065     return d->mName == person.d->mName && d->mEmail == person.d->mEmail;
0066 }
0067 
0068 bool KCalendarCore::Person::operator!=(const Person &person) const
0069 {
0070     return !(*this == person);
0071 }
0072 
0073 Person &KCalendarCore::Person::operator=(const Person &person)
0074 {
0075     // check for self assignment
0076     if (&person == this) {
0077         return *this;
0078     }
0079 
0080     d = person.d;
0081     return *this;
0082 }
0083 
0084 QString KCalendarCore::fullNameHelper(const QString &name, const QString &email)
0085 {
0086     if (name.isEmpty()) {
0087         return email;
0088     }
0089     if (email.isEmpty()) {
0090         return name;
0091     }
0092     // Taken from KContacts::Addressee::fullEmail
0093     QString fullName = name;
0094     const QRegularExpression needQuotes(QStringLiteral("[^ 0-9A-Za-z\\x{0080}-\\x{FFFF}]"));
0095     bool weNeedToQuote = name.indexOf(needQuotes) != -1;
0096     if (weNeedToQuote) {
0097         if (fullName[0] != QLatin1Char('"')) {
0098             fullName.prepend(QLatin1Char('"'));
0099         }
0100         if (fullName[fullName.length() - 1] != QLatin1Char('"')) {
0101             fullName.append(QLatin1Char('"'));
0102         }
0103     }
0104     return fullName + QStringLiteral(" <") + email + QLatin1Char('>');
0105 }
0106 
0107 QString Person::fullName() const
0108 {
0109     return fullNameHelper(d->mName, d->mEmail);
0110 }
0111 
0112 QString Person::name() const
0113 {
0114     return d->mName;
0115 }
0116 
0117 QString Person::email() const
0118 {
0119     return d->mEmail;
0120 }
0121 
0122 bool Person::isEmpty() const
0123 {
0124     return d->mEmail.isEmpty() && d->mName.isEmpty();
0125 }
0126 
0127 void Person::setName(const QString &name)
0128 {
0129     d->mName = name;
0130 }
0131 
0132 void Person::setEmail(const QString &email)
0133 {
0134     if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
0135         d->mEmail = email.mid(7);
0136     } else {
0137         d->mEmail = email;
0138     }
0139 }
0140 
0141 bool Person::isValidEmail(const QString &email)
0142 {
0143     const int pos = email.lastIndexOf(QLatin1Char('@'));
0144     return (pos > 0) && (email.lastIndexOf(QLatin1Char('.')) > pos) && ((email.length() - pos) > 4);
0145 }
0146 
0147 uint KCalendarCore::qHash(const KCalendarCore::Person &key)
0148 {
0149     return qHash(key.fullName());
0150 }
0151 
0152 QDataStream &KCalendarCore::operator<<(QDataStream &stream, const KCalendarCore::Person &person)
0153 {
0154     return stream << person.d->mName << person.d->mEmail << (int)(0);
0155 }
0156 
0157 QDataStream &KCalendarCore::operator>>(QDataStream &stream, Person &person)
0158 {
0159     int count;
0160     stream >> person.d->mName >> person.d->mEmail >> count;
0161     return stream;
0162 }
0163 
0164 // The following function was lifted directly from KPIMUtils
0165 // in order to eliminate the dependency on that library.
0166 // Any changes made here should be ported there, and vice versa.
0167 static bool extractEmailAddressAndName(const QString &aStr, QString &mail, QString &name)
0168 {
0169     name.clear();
0170     mail.clear();
0171 
0172     const int len = aStr.length();
0173     const char cQuotes = '"';
0174 
0175     bool bInComment = false;
0176     bool bInQuotesOutsideOfEmail = false;
0177     int i = 0;
0178     int iAd = 0;
0179     int iMailStart = 0;
0180     int iMailEnd = 0;
0181     QChar c;
0182     unsigned int commentstack = 0;
0183 
0184     // Find the '@' of the email address
0185     // skipping all '@' inside "(...)" comments:
0186     while (i < len) {
0187         c = aStr[i];
0188         if (QLatin1Char('(') == c) {
0189             commentstack++;
0190         }
0191         if (QLatin1Char(')') == c) {
0192             commentstack--;
0193         }
0194         bInComment = commentstack != 0;
0195         if (QLatin1Char('"') == c && !bInComment) {
0196             bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
0197         }
0198 
0199         if (!bInComment && !bInQuotesOutsideOfEmail) {
0200             if (QLatin1Char('@') == c) {
0201                 iAd = i;
0202                 break; // found it
0203             }
0204         }
0205         ++i;
0206     }
0207 
0208     if (!iAd) {
0209         // We suppose the user is typing the string manually and just
0210         // has not finished typing the mail address part.
0211         // So we take everything that's left of the '<' as name and the rest as mail
0212         for (i = 0; len > i; ++i) {
0213             c = aStr[i];
0214             if (QLatin1Char('<') != c) {
0215                 name.append(c);
0216             } else {
0217                 break;
0218             }
0219         }
0220         mail = aStr.mid(i + 1);
0221         if (mail.endsWith(QLatin1Char('>'))) {
0222             mail.chop(1);
0223         }
0224 
0225     } else {
0226         // Loop backwards until we find the start of the string
0227         // or a ',' that is outside of a comment
0228         //          and outside of quoted text before the leading '<'.
0229         bInComment = false;
0230         bInQuotesOutsideOfEmail = false;
0231         for (i = iAd - 1; 0 <= i; --i) {
0232             c = aStr[i];
0233             if (bInComment) {
0234                 if (QLatin1Char('(') == c) {
0235                     if (!name.isEmpty()) {
0236                         name.prepend(QLatin1Char(' '));
0237                     }
0238                     bInComment = false;
0239                 } else {
0240                     name.prepend(c); // all comment stuff is part of the name
0241                 }
0242             } else if (bInQuotesOutsideOfEmail) {
0243                 if (QLatin1Char(cQuotes) == c) {
0244                     bInQuotesOutsideOfEmail = false;
0245                 } else if (c != QLatin1Char('\\')) {
0246                     name.prepend(c);
0247                 }
0248             } else {
0249                 // found the start of this addressee ?
0250                 if (QLatin1Char(',') == c) {
0251                     break;
0252                 }
0253                 // stuff is before the leading '<' ?
0254                 if (iMailStart) {
0255                     if (QLatin1Char(cQuotes) == c) {
0256                         bInQuotesOutsideOfEmail = true; // end of quoted text found
0257                     } else {
0258                         name.prepend(c);
0259                     }
0260                 } else {
0261                     switch (c.toLatin1()) {
0262                     case '<':
0263                         iMailStart = i;
0264                         break;
0265                     case ')':
0266                         if (!name.isEmpty()) {
0267                             name.prepend(QLatin1Char(' '));
0268                         }
0269                         bInComment = true;
0270                         break;
0271                     default:
0272                         if (QLatin1Char(' ') != c) {
0273                             mail.prepend(c);
0274                         }
0275                     }
0276                 }
0277             }
0278         }
0279 
0280         name = name.simplified();
0281         mail = mail.simplified();
0282 
0283         if (mail.isEmpty()) {
0284             return false;
0285         }
0286 
0287         mail.append(QLatin1Char('@'));
0288 
0289         // Loop forward until we find the end of the string
0290         // or a ',' that is outside of a comment
0291         //          and outside of quoted text behind the trailing '>'.
0292         bInComment = false;
0293         bInQuotesOutsideOfEmail = false;
0294         int parenthesesNesting = 0;
0295         for (i = iAd + 1; len > i; ++i) {
0296             c = aStr[i];
0297             if (bInComment) {
0298                 if (QLatin1Char(')') == c) {
0299                     if (--parenthesesNesting == 0) {
0300                         bInComment = false;
0301                         if (!name.isEmpty()) {
0302                             name.append(QLatin1Char(' '));
0303                         }
0304                     } else {
0305                         // nested ")", add it
0306                         name.append(QLatin1Char(')')); // name can't be empty here
0307                     }
0308                 } else {
0309                     if (QLatin1Char('(') == c) {
0310                         // nested "("
0311                         ++parenthesesNesting;
0312                     }
0313                     name.append(c); // all comment stuff is part of the name
0314                 }
0315             } else if (bInQuotesOutsideOfEmail) {
0316                 if (QLatin1Char(cQuotes) == c) {
0317                     bInQuotesOutsideOfEmail = false;
0318                 } else if (c != QLatin1Char('\\')) {
0319                     name.append(c);
0320                 }
0321             } else {
0322                 // found the end of this addressee ?
0323                 if (QLatin1Char(',') == c) {
0324                     break;
0325                 }
0326                 // stuff is behind the trailing '>' ?
0327                 if (iMailEnd) {
0328                     if (QLatin1Char(cQuotes) == c) {
0329                         bInQuotesOutsideOfEmail = true; // start of quoted text found
0330                     } else {
0331                         name.append(c);
0332                     }
0333                 } else {
0334                     switch (c.toLatin1()) {
0335                     case '>':
0336                         iMailEnd = i;
0337                         break;
0338                     case '(':
0339                         if (!name.isEmpty()) {
0340                             name.append(QLatin1Char(' '));
0341                         }
0342                         if (++parenthesesNesting > 0) {
0343                             bInComment = true;
0344                         }
0345                         break;
0346                     default:
0347                         if (QLatin1Char(' ') != c) {
0348                             mail.append(c);
0349                         }
0350                     }
0351                 }
0352             }
0353         }
0354     }
0355 
0356     name = name.simplified();
0357     mail = mail.simplified();
0358 
0359     return !(name.isEmpty() || mail.isEmpty());
0360 }
0361 
0362 Person Person::fromFullName(const QString &fullName)
0363 {
0364     QString email;
0365     QString name;
0366     extractEmailAddressAndName(fullName, email, name);
0367     return Person(name, email);
0368 }
0369 
0370 #include "moc_person.cpp"