File indexing completed on 2024-03-24 03:55:38
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 size_t KCalendarCore::qHash(const KCalendarCore::Person &key, size_t seed) 0148 { 0149 return qHash(key.fullName(), seed); 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"