File indexing completed on 2024-05-12 05:14:49
0001 /* 0002 * kamail.cpp - email functions 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2002-2022 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kamail.h" 0010 0011 #include "kalarm.h" 0012 #include "kalarmapp.h" 0013 #include "mainwindow.h" 0014 #include "preferences.h" 0015 #include "lib/messagebox.h" 0016 #include "kalarmcalendar/identities.h" 0017 #include "akonadiplugin/akonadiplugin.h" 0018 #include "kalarm_debug.h" 0019 0020 #include "kmailinterface.h" 0021 0022 #include <KIdentityManagementCore/IdentityManager> 0023 #include <KIdentityManagementCore/Identity> 0024 #include <KMime/HeaderParsing> 0025 #include <KMime/Headers> 0026 #include <KMime/Message> 0027 0028 #include <KEmailAddress> 0029 #include <KAboutData> 0030 #include <KLocalizedString> 0031 #include <KFileItem> 0032 #include <KIO/StatJob> 0033 #include <KIO/StoredTransferJob> 0034 #include <KJobWidgets> 0035 #include <KEMailSettings> 0036 #include <KCodecs> 0037 #include <KShell> 0038 0039 #include <QUrl> 0040 #include <QFile> 0041 #include <QHostInfo> 0042 #include <QList> 0043 #include <QByteArray> 0044 #include <QTextCodec> 0045 #include <QStandardPaths> 0046 #include <QDBusInterface> 0047 0048 #ifdef Q_OS_WIN 0049 #define popen _popen 0050 #define pclose _pclose 0051 #endif 0052 0053 using namespace MailSend; 0054 0055 namespace HeaderParsing 0056 { 0057 bool parseAddress(const char* & scursor, const char* const send, 0058 KMime::Types::Address& result, bool isCRLF = false); 0059 } 0060 0061 namespace 0062 { 0063 void initHeaders(KMime::Message&, JobData&); 0064 KMime::Types::Mailbox::List parseAddresses(const QString& text, QString& invalidItem); 0065 QString extractEmailAndNormalize(const QString& emailAddress); 0066 QByteArray autoDetectCharset(const QString& text); 0067 } 0068 0069 QString KAMail::i18n_NeedFromEmailAddress() 0070 { return i18nc("@info", "A 'From' email address must be configured in order to execute email alarms."); } 0071 0072 QString KAMail::i18n_sent_mail() 0073 { return i18nc("@info KMail folder name: this should be translated the same as in kmail", "sent-mail"); } 0074 0075 KAMail* KAMail::mInstance = nullptr; // used only to enable signals/slots to work 0076 AkonadiPlugin* KAMail::mAkonadiPlugin = nullptr; 0077 0078 KAMail* KAMail::instance() 0079 { 0080 if (!mInstance) 0081 mInstance = new KAMail(); 0082 return mInstance; 0083 } 0084 0085 /****************************************************************************** 0086 * Send the email message specified in an event. 0087 * Reply = 1 if the message was sent - 'errmsgs' may contain copy error messages. 0088 * = 0 if the message is queued for sending. 0089 * = -1 if the message was not sent - 'errmsgs' contains the error messages. 0090 */ 0091 int KAMail::send(JobData& jobdata, QStringList& errmsgs) 0092 { 0093 QString err; 0094 KIdentityManagementCore::Identity identity; 0095 jobdata.from = Preferences::emailAddress(); 0096 if (jobdata.event.emailFromId() 0097 && Preferences::emailFrom() == Preferences::MAIL_FROM_KMAIL) 0098 { 0099 identity = Identities::identityManager()->identityForUoid(jobdata.event.emailFromId()); 0100 if (identity.isNull()) 0101 { 0102 qCCritical(KALARM_LOG) << "KAMail::send: Identity" << jobdata.event.emailFromId() << "not found"; 0103 errmsgs = errors(xi18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> not found", jobdata.event.emailFromId())); 0104 return -1; 0105 } 0106 if (identity.primaryEmailAddress().isEmpty()) 0107 { 0108 qCCritical(KALARM_LOG) << "KAMail::send: Identity" << identity.identityName() << "uoid" << identity.uoid() << ": no email address"; 0109 errmsgs = errors(xi18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> has no email address", identity.identityName())); 0110 return -1; 0111 } 0112 jobdata.from = identity.fullEmailAddr(); 0113 } 0114 if (jobdata.from.isEmpty()) 0115 { 0116 switch (Preferences::emailFrom()) 0117 { 0118 case Preferences::MAIL_FROM_KMAIL: 0119 errmsgs = errors(xi18nc("@info", "<para>No 'From' email address is configured (no default email identity found)</para>" 0120 "<para>Please set it in <application>KMail</application> or in the <application>KAlarm</application> Configuration dialog.</para>")); 0121 break; 0122 case Preferences::MAIL_FROM_SYS_SETTINGS: 0123 errmsgs = errors(xi18nc("@info", "<para>No 'From' email address is configured.</para>" 0124 "<para>Please set a default address in <application>KMail</application> or in the <application>KAlarm</application> Configuration dialog.</para>")); 0125 break; 0126 case Preferences::MAIL_FROM_ADDR: 0127 default: 0128 errmsgs = errors(xi18nc("@info", "<para>No 'From' email address is configured.</para>" 0129 "<para>Please set it in the <application>KAlarm</application> Configuration dialog.</para>")); 0130 break; 0131 } 0132 return -1; 0133 } 0134 jobdata.bcc = (jobdata.event.emailBcc() ? Preferences::emailBccAddress() : QString()); 0135 qCDebug(KALARM_LOG) << "KAMail::send: To:" << jobdata.event.emailAddresses(QStringLiteral(",")) 0136 << "\nSubject:" << jobdata.event.emailSubject(); 0137 0138 KMime::Message::Ptr message = KMime::Message::Ptr(new KMime::Message); 0139 0140 if (Preferences::emailClient() == Preferences::sendmail) 0141 { 0142 qCDebug(KALARM_LOG) << "KAMail::send: Sending via sendmail"; 0143 const QStringList paths{QStringLiteral("/sbin"), QStringLiteral("/usr/sbin"), QStringLiteral("/usr/lib")}; 0144 QString command = QStandardPaths::findExecutable(QStringLiteral("sendmail"), paths); 0145 if (!command.isNull()) 0146 { 0147 command += QStringLiteral(" -f "); 0148 command += extractEmailAndNormalize(jobdata.from); 0149 command += QStringLiteral(" -oi -t "); 0150 initHeaders(*message, jobdata); 0151 } 0152 else 0153 { 0154 command = QStandardPaths::findExecutable(QStringLiteral("mail"), paths); 0155 if (command.isNull()) 0156 { 0157 qCCritical(KALARM_LOG) << "KAMail::send: sendmail not found"; 0158 errmsgs = errors(xi18nc("@info", "<command>%1</command> not found", QStringLiteral("sendmail"))); // give up 0159 return -1; 0160 } 0161 0162 command += QStringLiteral(" -s "); 0163 command += KShell::quoteArg(jobdata.event.emailSubject()); 0164 0165 if (!jobdata.bcc.isEmpty()) 0166 { 0167 command += QStringLiteral(" -b "); 0168 command += extractEmailAndNormalize(jobdata.bcc); 0169 } 0170 0171 command += QLatin1Char(' '); 0172 command += jobdata.event.emailPureAddresses(QStringLiteral(" ")); // locally provided, okay 0173 } 0174 // Add the body and attachments to the message. 0175 // (Sendmail requires attachments to have already been included in the message.) 0176 err = appendBodyAttachments(*message, jobdata); 0177 if (!err.isNull()) 0178 { 0179 qCCritical(KALARM_LOG) << "KAMail::send: Error compiling message:" << err; 0180 errmsgs = errors(err); 0181 return -1; 0182 } 0183 0184 // Execute the send command 0185 FILE* fd = ::popen(command.toLocal8Bit().constData(), "w"); 0186 if (!fd) 0187 { 0188 qCCritical(KALARM_LOG) << "KAMail::send: Unable to open a pipe to " << command; 0189 errmsgs = errors(); 0190 return -1; 0191 } 0192 message->assemble(); 0193 QByteArray encoded = message->encodedContent(); 0194 fwrite(encoded.constData(), encoded.length(), 1, fd); 0195 pclose(fd); 0196 0197 if (jobdata.allowNotify) 0198 notifyQueued(jobdata.event); 0199 return 1; 0200 } 0201 else if (Preferences::useAkonadi()) 0202 { 0203 qCDebug(KALARM_LOG) << "KAMail::send: Sending via KDE"; 0204 initHeaders(*message, jobdata); 0205 err = appendBodyAttachments(*message, jobdata); 0206 if (!err.isNull()) 0207 { 0208 qCCritical(KALARM_LOG) << "KAMail::send: Error compiling message:" << err; 0209 errmsgs = errors(err); 0210 return -1; 0211 } 0212 0213 if (!mAkonadiPlugin) 0214 { 0215 mAkonadiPlugin = Preferences::akonadiPlugin(); 0216 connect(mAkonadiPlugin, &AkonadiPlugin::emailSent, instance(), &KAMail::akonadiEmailSent); 0217 connect(mAkonadiPlugin, &AkonadiPlugin::emailQueued, instance(), [](const KAEvent& e) { notifyQueued(e); }); 0218 } 0219 const bool keepEmail = (Preferences::emailClient() == Preferences::kmail || Preferences::emailCopyToKMail()); 0220 err = mAkonadiPlugin->sendMail(message, identity, extractEmailAndNormalize(jobdata.from), keepEmail, jobdata); 0221 if (!err.isEmpty()) 0222 { 0223 errmsgs = errors(err); 0224 return -1; 0225 } 0226 } 0227 return 0; 0228 } 0229 0230 /****************************************************************************** 0231 * Called when sending an email via Akonadi is complete. 0232 */ 0233 void KAMail::akonadiEmailSent(const JobData& jobdata, const QStringList& errmsgs, bool sendError) 0234 { 0235 QStringList messages = errmsgs; 0236 if (!errmsgs.isEmpty() && sendError) 0237 messages = errors(errmsgs.at(0), SEND_ERROR); 0238 theApp()->emailSent(jobdata, messages); 0239 } 0240 0241 /****************************************************************************** 0242 * Append the body and attachments to the email text. 0243 * Reply = reason for error 0244 * = empty string if successful. 0245 */ 0246 QString KAMail::appendBodyAttachments(KMime::Message& message, JobData& data) 0247 { 0248 const QStringList attachments = data.event.emailAttachments(); 0249 if (attachments.isEmpty()) 0250 { 0251 // There are no attachments, so simply append the message body 0252 message.contentType()->setMimeType("text/plain"); 0253 message.contentType()->setCharset("utf-8"); 0254 message.fromUnicodeString(data.event.message()); 0255 auto encodings = KMime::encodingsForData(message.body()); 0256 encodings.removeAll(KMime::Headers::CE8Bit); // not handled by KMime 0257 message.contentTransferEncoding()->setEncoding(encodings.at(0)); 0258 message.assemble(); 0259 } 0260 else 0261 { 0262 // There are attachments, so the message must be in MIME format 0263 message.contentType()->setMimeType("multipart/mixed"); 0264 message.contentType()->setBoundary(KMime::multiPartBoundary()); 0265 0266 if (!data.event.message().isEmpty()) 0267 { 0268 // There is a message body 0269 auto content = new KMime::Content(); 0270 content->contentType()->setMimeType("text/plain"); 0271 content->contentType()->setCharset("utf-8"); 0272 content->fromUnicodeString(data.event.message()); 0273 auto encodings = KMime::encodingsForData(content->body()); 0274 encodings.removeAll(KMime::Headers::CE8Bit); // not handled by KMime 0275 content->contentTransferEncoding()->setEncoding(encodings.at(0)); 0276 content->assemble(); 0277 message.appendContent(content); 0278 } 0279 0280 // Append each attachment in turn 0281 for (const QString& att : attachments) 0282 { 0283 const QString attachment = QString::fromLatin1(att.toLocal8Bit()); 0284 const QUrl url = QUrl::fromUserInput(attachment, QString(), QUrl::AssumeLocalFile); 0285 const QString attachError = xi18nc("@info", "Error attaching file: <filename>%1</filename>", attachment); 0286 QByteArray contents; 0287 bool atterror = false; 0288 if (!url.isLocalFile()) 0289 { 0290 auto statJob = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatDetail::StatDefaultDetails); 0291 KJobWidgets::setWindow(statJob, MainWindow::mainMainWindow()); 0292 if (!statJob->exec()) 0293 { 0294 qCCritical(KALARM_LOG) << "KAMail::appendBodyAttachments: Not found:" << attachment; 0295 return xi18nc("@info", "Attachment not found: <filename>%1</filename>", attachment); 0296 } 0297 KFileItem fi(statJob->statResult(), url); 0298 if (fi.isDir() || !fi.isReadable()) 0299 { 0300 qCCritical(KALARM_LOG) << "KAMail::appendBodyAttachments: Not file/not readable:" << attachment; 0301 return attachError; 0302 } 0303 0304 // Read the file contents 0305 auto downloadJob = KIO::storedGet(url); 0306 KJobWidgets::setWindow(downloadJob, MainWindow::mainMainWindow()); 0307 if (!downloadJob->exec()) 0308 { 0309 qCCritical(KALARM_LOG) << "KAMail::appendBodyAttachments: Load failure:" << attachment; 0310 return attachError; 0311 } 0312 contents = downloadJob->data(); 0313 if (static_cast<unsigned>(contents.size()) < fi.size()) 0314 { 0315 qCDebug(KALARM_LOG) << "KAMail::appendBodyAttachments: Read error:" << attachment; 0316 atterror = true; 0317 } 0318 } 0319 else 0320 { 0321 QFile f(url.toLocalFile()); 0322 if (!f.open(QIODevice::ReadOnly)) 0323 { 0324 qCCritical(KALARM_LOG) << "KAMail::appendBodyAttachments: Load failure:" << attachment; 0325 return attachError; 0326 } 0327 contents = f.readAll(); 0328 } 0329 0330 QByteArray coded = KCodecs::base64Encode(contents); 0331 auto content = new KMime::Content(); 0332 content->setBody(coded + "\n\n"); 0333 0334 // Set the content type 0335 QMimeDatabase mimeDb; 0336 QString typeName = mimeDb.mimeTypeForUrl(url).name(); 0337 auto ctype = new KMime::Headers::ContentType; 0338 ctype->fromUnicodeString(typeName, autoDetectCharset(typeName)); 0339 ctype->setName(attachment, "local"); 0340 content->setHeader(ctype); 0341 0342 // Set the encoding 0343 auto cte = new KMime::Headers::ContentTransferEncoding; 0344 cte->setEncoding(KMime::Headers::CEbase64); 0345 cte->setDecoded(false); 0346 content->setHeader(cte); 0347 content->assemble(); 0348 message.appendContent(content); 0349 if (atterror) 0350 return attachError; 0351 } 0352 message.assemble(); 0353 } 0354 return {}; 0355 } 0356 0357 /****************************************************************************** 0358 * If any of the destination email addresses are non-local, display a 0359 * notification message saying that an email has been queued for sending. 0360 */ 0361 void KAMail::notifyQueued(const KAEvent& event) 0362 { 0363 KMime::Types::Address addr; 0364 const QString localhost = QStringLiteral("localhost"); 0365 const QString hostname = QHostInfo::localHostName(); 0366 const KCalendarCore::Person::List addresses = event.emailAddressees(); 0367 for (const KCalendarCore::Person& address : addresses) 0368 { 0369 const QByteArray email = address.email().toLocal8Bit(); 0370 const char* em = email.constData(); 0371 if (!email.isEmpty() 0372 && HeaderParsing::parseAddress(em, em + email.length(), addr)) 0373 { 0374 const QString domain = addr.mailboxList.at(0).addrSpec().domain; 0375 if (!domain.isEmpty() && domain != localhost && domain != hostname) 0376 { 0377 KAMessageBox::information(MainWindow::mainMainWindow(), i18nc("@info", "An email has been queued to be sent"), QString(), Preferences::EMAIL_QUEUED_NOTIFY); 0378 return; 0379 } 0380 } 0381 } 0382 } 0383 0384 /****************************************************************************** 0385 * Fetch the user's email address configured in KMail. 0386 */ 0387 QString KAMail::controlCentreAddress() 0388 { 0389 KEMailSettings e; 0390 return e.getSetting(KEMailSettings::EmailAddress); 0391 } 0392 0393 /****************************************************************************** 0394 * Parse a list of email addresses, optionally containing display names, 0395 * entered by the user. 0396 * Reply = the invalid item if error, else empty string. 0397 */ 0398 QString KAMail::convertAddresses(const QString& items, KCalendarCore::Person::List& list) 0399 { 0400 list.clear(); 0401 QString invalidItem; 0402 const KMime::Types::Mailbox::List mailboxes = parseAddresses(items, invalidItem); 0403 if (!invalidItem.isEmpty()) 0404 return invalidItem; 0405 for (const KMime::Types::Mailbox& mailbox : mailboxes) 0406 { 0407 const KCalendarCore::Person person(mailbox.name(), mailbox.addrSpec().asString()); 0408 list += person; 0409 } 0410 return {}; 0411 } 0412 0413 /****************************************************************************** 0414 * Check the validity of an email address. 0415 * Because internal email addresses don't have to abide by the usual internet 0416 * email address rules, only some basic checks are made. 0417 * Reply = 1 if alright, 0 if empty, -1 if error. 0418 */ 0419 int KAMail::checkAddress(QString& address) 0420 { 0421 address = address.trimmed(); 0422 // Check that there are no list separator characters present 0423 if (address.indexOf(QLatin1Char(',')) >= 0 || address.indexOf(QLatin1Char(';')) >= 0) 0424 return -1; 0425 const int n = address.length(); 0426 if (!n) 0427 return 0; 0428 int start = 0; 0429 int end = n - 1; 0430 if (address.at(end) == QLatin1Char('>')) 0431 { 0432 // The email address is in <...> 0433 if ((start = address.indexOf(QLatin1Char('<'))) < 0) 0434 return -1; 0435 ++start; 0436 --end; 0437 } 0438 const int i = address.indexOf(QLatin1Char('@'), start); 0439 if (i >= 0) 0440 { 0441 if (i == start || i == end) // check @ isn't the first or last character 0442 // || address.indexOf(QLatin1Char('@'), i + 1) >= 0) // check for multiple @ characters 0443 return -1; 0444 } 0445 /* else 0446 { 0447 // Allow the @ character to be missing if it's a local user 0448 if (!getpwnam(address.mid(start, end - start + 1).toLocal8Bit())) 0449 return false; 0450 } 0451 for (int i = start; i <= end; ++i) 0452 { 0453 char ch = address.at(i).toLatin1(); 0454 if (ch == '.' || ch == '@' || ch == '-' || ch == '_' 0455 || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') 0456 || (ch >= '0' && ch <= '9')) 0457 continue; 0458 return false; 0459 }*/ 0460 return 1; 0461 } 0462 0463 /****************************************************************************** 0464 * Convert a comma or semicolon delimited list of attachments into a 0465 * QStringList. The items are checked for validity. 0466 * Reply = the invalid item if error, else empty string. 0467 */ 0468 QString KAMail::convertAttachments(const QString& items, QStringList& list) 0469 { 0470 list.clear(); 0471 int length = items.length(); 0472 for (int next = 0; next < length; ) 0473 { 0474 // Find the first delimiter character (, or ;) 0475 int i = items.indexOf(QLatin1Char(','), next); 0476 if (i < 0) 0477 i = items.length(); 0478 int sc = items.indexOf(QLatin1Char(';'), next); 0479 if (sc < 0) 0480 sc = items.length(); 0481 if (sc < i) 0482 i = sc; 0483 QString item = items.mid(next, i - next).trimmed(); 0484 switch (checkAttachment(item)) 0485 { 0486 case 1: list += item; break; 0487 case 0: break; // empty attachment name 0488 case -1: 0489 default: return item; // error 0490 } 0491 next = i + 1; 0492 } 0493 return {}; 0494 } 0495 0496 /****************************************************************************** 0497 * Check for the existence of the attachment file. 0498 * If non-null, '*url' receives the QUrl of the attachment. 0499 * Reply = 1 if attachment exists 0500 * = 0 if null name 0501 * = -1 if doesn't exist. 0502 */ 0503 int KAMail::checkAttachment(QString& attachment, QUrl* url) 0504 { 0505 attachment = attachment.trimmed(); 0506 if (attachment.isEmpty()) 0507 { 0508 if (url) 0509 *url = QUrl(); 0510 return 0; 0511 } 0512 // Check that the file exists 0513 QUrl u = QUrl::fromUserInput(attachment, QString(), QUrl::AssumeLocalFile); 0514 u.setPath(QDir::cleanPath(u.path())); 0515 if (url) 0516 *url = u; 0517 return checkAttachment(u) ? 1 : -1; 0518 } 0519 0520 /****************************************************************************** 0521 * Check for the existence of the attachment file. 0522 */ 0523 bool KAMail::checkAttachment(const QUrl& url) 0524 { 0525 auto statJob = KIO::stat(url); 0526 KJobWidgets::setWindow(statJob, MainWindow::mainMainWindow()); 0527 if (!statJob->exec()) 0528 return false; // doesn't exist 0529 KFileItem fi(statJob->statResult(), url); 0530 if (fi.isDir() || !fi.isReadable()) 0531 return false; 0532 return true; 0533 } 0534 0535 /****************************************************************************** 0536 * Set the appropriate error messages for a given error string. 0537 */ 0538 QStringList KAMail::errors(const QString& err, ErrType prefix) 0539 { 0540 QString error1; 0541 switch (prefix) 0542 { 0543 case SEND_FAIL: error1 = i18nc("@info", "Failed to send email"); break; 0544 case SEND_ERROR: error1 = i18nc("@info", "Error sending email"); break; 0545 } 0546 if (err.isEmpty()) 0547 return QStringList(error1); 0548 return QStringList{QStringLiteral("%1:").arg(error1), err}; 0549 } 0550 0551 namespace 0552 { 0553 0554 /****************************************************************************** 0555 * Create the headers part of the email. 0556 */ 0557 void initHeaders(KMime::Message& message, JobData& data) 0558 { 0559 auto date = new KMime::Headers::Date; 0560 date->setDateTime(KADateTime::currentDateTime(Preferences::timeSpec()).qDateTime()); 0561 message.setHeader(date); 0562 0563 auto from = new KMime::Headers::From; 0564 from->fromUnicodeString(data.from, autoDetectCharset(data.from)); 0565 message.setHeader(from); 0566 0567 auto to = new KMime::Headers::To; 0568 const KCalendarCore::Person::List toList = data.event.emailAddressees(); 0569 for (const KCalendarCore::Person& who : toList) 0570 to->addAddress(who.email().toLatin1(), who.name()); 0571 message.setHeader(to); 0572 0573 if (!data.bcc.isEmpty()) 0574 { 0575 auto bcc = new KMime::Headers::Bcc; 0576 bcc->fromUnicodeString(data.bcc, autoDetectCharset(data.bcc)); 0577 message.setHeader(bcc); 0578 } 0579 0580 auto subject = new KMime::Headers::Subject; 0581 const QString str = data.event.emailSubject(); 0582 subject->fromUnicodeString(str, autoDetectCharset(str)); 0583 message.setHeader(subject); 0584 0585 auto agent = new KMime::Headers::UserAgent; 0586 agent->fromUnicodeString(KAboutData::applicationData().displayName() + QLatin1String("/" KALARM_VERSION), "us-ascii"); 0587 message.setHeader(agent); 0588 0589 auto id = new KMime::Headers::MessageID; 0590 id->generate(data.from.mid(data.from.indexOf(QLatin1Char('@')) + 1).toLatin1()); 0591 message.setHeader(id); 0592 } 0593 0594 /****************************************************************************** 0595 * Extract the pure addresses from given email addresses. 0596 */ 0597 QString extractEmailAndNormalize(const QString& emailAddress) 0598 { 0599 return KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(emailAddress)); 0600 } 0601 0602 //----------------------------------------------------------------------------- 0603 // Based on KMail KMMsgBase::autoDetectCharset(). 0604 QByteArray autoDetectCharset(const QString& text) 0605 { 0606 for (QByteArray encoding : {"us-ascii", "iso-8859-1", "locale", "utf-8"}) 0607 { 0608 if (encoding == "locale") 0609 encoding = QTextCodec::codecForLocale()->name().toLower(); 0610 if (text.isEmpty()) 0611 return encoding; 0612 if (encoding == "us-ascii") 0613 { 0614 if (KMime::isUsAscii(text)) 0615 return encoding; 0616 } 0617 else 0618 { 0619 const QTextCodec* codec = QTextCodec::codecForName(encoding.toLower()); 0620 if (!codec) 0621 qCDebug(KALARM_LOG) << "KAMail::autoDetectCharset: Something is wrong and I cannot get a codec. [" << encoding <<"]"; 0622 else 0623 { 0624 if (codec->canEncode(text)) 0625 return encoding; 0626 } 0627 } 0628 } 0629 return {}; 0630 } 0631 0632 /****************************************************************************** 0633 * Parse a string containing multiple addresses, separated by comma or semicolon, 0634 * while retaining Unicode name parts. 0635 * Note that this only needs to parse strings input into KAlarm, so it only 0636 * needs to accept the common syntax for email addresses, not obsolete syntax. 0637 */ 0638 KMime::Types::Mailbox::List parseAddresses(const QString& text, QString& invalidItem) 0639 { 0640 KMime::Types::Mailbox::List list; 0641 int state = 0; 0642 int start = 0; // start of this item 0643 int endName = 0; // character after end of name 0644 int startAddr = 0; // start of address 0645 int endAddr = 0; // character after end of address 0646 char lastch = '\0'; 0647 bool ended = false; // found the end of the item 0648 for (int i = 0, count = text.length(); i <= count; ++i) 0649 { 0650 if (i == count) 0651 ended = true; 0652 else 0653 { 0654 const char ch = text[i].toLatin1(); 0655 switch (state) 0656 { 0657 case 0: // looking for start of item 0658 if (ch == ' ' || ch == '\t') 0659 continue; 0660 start = i; 0661 state = (ch == '"') ? 10 : 1; 0662 break; 0663 case 1: // looking for start of address, or end of item 0664 switch (ch) 0665 { 0666 case '<': 0667 startAddr = i + 1; 0668 state = 2; 0669 break; 0670 case ',': 0671 case ';': 0672 ended = true; 0673 break; 0674 case ' ': 0675 break; 0676 default: 0677 endName = i + 1; 0678 break; 0679 } 0680 break; 0681 case 2: // looking for '>' at end of address 0682 if (ch == '>') 0683 { 0684 endAddr = i; 0685 state = 3; 0686 } 0687 break; 0688 case 3: // looking for item separator 0689 if (ch == ',' || ch == ';') 0690 ended = true; 0691 else if (ch != ' ') 0692 { 0693 invalidItem = text.mid(start); 0694 return {}; 0695 } 0696 break; 0697 case 10: // looking for closing quote 0698 if (ch == '"' && lastch != '\\') 0699 { 0700 ++start; // remove opening quote from name 0701 endName = i; 0702 state = 11; 0703 } 0704 lastch = ch; 0705 break; 0706 case 11: // looking for '<' 0707 if (ch == '<') 0708 { 0709 startAddr = i + 1; 0710 state = 2; 0711 } 0712 break; 0713 } 0714 } 0715 if (ended) 0716 { 0717 // Found the end of the item - add it to the list 0718 if (!startAddr) 0719 { 0720 startAddr = start; 0721 endAddr = endName; 0722 endName = 0; 0723 } 0724 const QString addr = text.mid(startAddr, endAddr - startAddr); 0725 KMime::Types::Mailbox mbox; 0726 mbox.fromUnicodeString(addr); 0727 if (mbox.address().isEmpty()) 0728 { 0729 invalidItem = text.mid(start, endAddr - start); 0730 return {}; 0731 } 0732 if (endName) 0733 { 0734 int len = endName - start; 0735 QString name = text.mid(start, endName - start); 0736 if (name.at(0) == QLatin1Char('"') && name.at(len - 1) == QLatin1Char('"')) 0737 name = name.mid(1, len - 2); 0738 mbox.setName(name); 0739 } 0740 list.append(mbox); 0741 0742 endName = startAddr = endAddr = 0; 0743 start = i + 1; 0744 state = 0; 0745 ended = false; 0746 } 0747 } 0748 return list; 0749 } 0750 0751 } // namespace 0752 0753 /*============================================================================= 0754 = HeaderParsing : modified and additional functions. 0755 = The following functions are modified from, or additional to, those in 0756 = libkmime kmime_header_parsing.cpp. 0757 =============================================================================*/ 0758 0759 namespace HeaderParsing 0760 { 0761 0762 using namespace KMime; 0763 using namespace KMime::Types; 0764 using namespace KMime::HeaderParsing; 0765 0766 /****************************************************************************** 0767 * New function. 0768 * Allow a local user name to be specified as an email address. 0769 */ 0770 bool parseUserName(const char* & scursor, const char* const send, QString& result, bool isCRLF) 0771 { 0772 QByteArray atom; 0773 0774 if (scursor != send) 0775 { 0776 // first, eat any whitespace 0777 eatCFWS(scursor, send, isCRLF); 0778 0779 char ch = *scursor++; 0780 switch (ch) 0781 { 0782 case '.': // dot 0783 case '@': 0784 case '"': // quoted-string 0785 return false; 0786 0787 default: // atom 0788 scursor--; // re-set scursor to point to ch again 0789 if (parseAtom(scursor, send, atom, false /* no 8bit */)) 0790 { 0791 //TODO FIXME on windows 0792 #ifndef WIN32 0793 if (getpwnam(atom.constData())) 0794 { 0795 result = QLatin1String(atom); 0796 return true; 0797 } 0798 #endif 0799 } 0800 return false; // parseAtom can only fail if the first char is non-atext. 0801 } 0802 } 0803 return false; 0804 } 0805 0806 /****************************************************************************** 0807 * Modified function. 0808 * Allow a local user name to be specified as an email address, and reinstate 0809 * the original scursor on error return. 0810 */ 0811 bool parseAddress(const char* & scursor, const char* const send, Address& result, bool isCRLF) 0812 { 0813 // address := mailbox / group 0814 0815 eatCFWS(scursor, send, isCRLF); 0816 if (scursor == send) 0817 return false; 0818 0819 // first try if it's a single mailbox: 0820 Mailbox maybeMailbox; 0821 const char * oldscursor = scursor; 0822 if (parseMailbox(scursor, send, maybeMailbox, isCRLF)) 0823 { 0824 // yes, it is: 0825 result.displayName.clear(); 0826 result.mailboxList.append(maybeMailbox); 0827 return true; 0828 } 0829 scursor = oldscursor; 0830 0831 // KAlarm: Allow a local user name to be specified 0832 // no, it's not a single mailbox. Try if it's a local user name: 0833 QString maybeUserName; 0834 if (parseUserName(scursor, send, maybeUserName, isCRLF)) 0835 { 0836 // yes, it is: 0837 maybeMailbox.setName(QString()); 0838 AddrSpec addrSpec; 0839 addrSpec.localPart = maybeUserName; 0840 addrSpec.domain.clear(); 0841 maybeMailbox.setAddress(addrSpec); 0842 result.displayName.clear(); 0843 result.mailboxList.append(maybeMailbox); 0844 return true; 0845 } 0846 scursor = oldscursor; 0847 0848 Address maybeAddress; 0849 0850 // no, it's not a single mailbox. Try if it's a group: 0851 if (!parseGroup(scursor, send, maybeAddress, isCRLF)) 0852 { 0853 scursor = oldscursor; // KAlarm: reinstate original scursor on error return 0854 return false; 0855 } 0856 0857 result = maybeAddress; 0858 return true; 0859 } 0860 0861 } // namespace HeaderParsing 0862 0863 #include "moc_kamail.cpp" 0864 0865 // vim: et sw=4: