Build with clang.
[kdepim.git] / kalarm / kamail.cpp
blob417ad466606961dd30c71e7baf876442e9baec1f
1 /*
2 * kamail.cpp - email functions
3 * Program: kalarm
4 * Copyright © 2002-2011 by David Jarvie <djarvie@kde.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "kalarm.h" //krazy:exclude=includes (kalarm.h must be first)
22 #include "kamail.h"
24 #include "functions.h"
25 #include "identities.h"
26 #include "kalarmapp.h"
27 #include "mainwindow.h"
28 #include "messagebox.h"
29 #include "preferences.h"
31 #include <kpimidentities/identitymanager.h>
32 #include <kpimidentities/identity.h>
33 #include <kpimutils/email.h>
34 #include <mailtransport/transportmanager.h>
35 #include <mailtransport/transport.h>
36 #include <mailtransport/messagequeuejob.h>
37 #ifdef USE_AKONADI
38 #include <kcalcore/person.h>
39 #else
40 #include <kcal/person.h>
41 #endif
42 #include <kmime/kmime_header_parsing.h>
43 #include <kmime/kmime_headers.h>
44 #include <kmime/kmime_message.h>
46 #include <kstandarddirs.h>
47 #include <klocale.h>
48 #include <kaboutdata.h>
49 #include <kfileitem.h>
50 #include <kio/netaccess.h>
51 #include <ktemporaryfile.h>
52 #include <kemailsettings.h>
53 #include <kcodecs.h>
54 #include <kcharsets.h>
55 #include <kascii.h>
56 #include <kdebug.h>
58 #include <QFile>
59 #include <QHostInfo>
60 #include <QList>
61 #include <QRegExp>
62 #include <QByteArray>
63 #include <QTextStream>
64 #include <QTextCodec>
65 #include <QtDBus/QtDBus>
67 #include <pwd.h>
69 #ifdef KMAIL_SUPPORTED
70 #include "kmailinterface.h"
72 static const char* KMAIL_DBUS_SERVICE = "org.kde.kmail";
73 //static const char* KMAIL_DBUS_PATH = "/KMail";
74 #endif
76 namespace HeaderParsing
78 bool parseAddress( const char* & scursor, const char * const send,
79 KMime::Types::Address & result, bool isCRLF=false );
82 static void initHeaders(KMime::Message&, KAMail::JobData&);
83 static KMime::Types::Mailbox::List parseAddresses(const QString& text, QString& invalidItem);
84 static QByteArray autoDetectCharset(const QString& text);
85 static const QTextCodec* codecForName(const QByteArray& str);
87 QString KAMail::i18n_NeedFromEmailAddress()
88 { return i18nc("@info/plain", "A 'From' email address must be configured in order to execute email alarms."); }
90 QString KAMail::i18n_sent_mail()
91 { return i18nc("@info/plain KMail folder name: this should be translated the same as in kmail", "sent-mail"); }
93 KAMail* KAMail::mInstance = 0; // used only to enable signals/slots to work
94 QQueue<MailTransport::MessageQueueJob*> KAMail::mJobs;
95 QQueue<KAMail::JobData> KAMail::mJobData;
97 KAMail* KAMail::instance()
99 if (!mInstance)
100 mInstance = new KAMail();
101 return mInstance;
105 /******************************************************************************
106 * Send the email message specified in an event.
107 * Reply = 1 if the message was sent - 'errmsgs' may contain copy error messages.
108 * = 0 if the message is queued for sending.
109 * = -1 if the message was not sent - 'errmsgs' contains the error messages.
111 int KAMail::send(JobData& jobdata, QStringList& errmsgs)
113 QString err;
114 KPIMIdentities::Identity identity;
115 if (!jobdata.event.emailFromId())
116 jobdata.from = Preferences::emailAddress();
117 else
119 identity = Identities::identityManager()->identityForUoid(jobdata.event.emailFromId());
120 if (identity.isNull())
122 kError() << "Identity" << jobdata.event.emailFromId() << "not found";
123 errmsgs = errors(i18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> not found", jobdata.event.emailFromId()));
124 return -1;
126 if (identity.primaryEmailAddress().isEmpty())
128 kError() << "Identity" << identity.identityName() << "uoid" << identity.uoid() << ": no email address";
129 errmsgs = errors(i18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> has no email address", identity.identityName()));
130 return -1;
132 jobdata.from = identity.fullEmailAddr();
134 if (jobdata.from.isEmpty())
136 switch (Preferences::emailFrom())
138 case Preferences::MAIL_FROM_KMAIL:
139 errmsgs = errors(i18nc("@info", "<para>No 'From' email address is configured (no default email identity found)</para>"
140 "<para>Please set it in <application>KMail</application> or in the <application>KAlarm</application> Configuration dialog.</para>"));
141 break;
142 case Preferences::MAIL_FROM_SYS_SETTINGS:
143 errmsgs = errors(i18nc("@info", "<para>No 'From' email address is configured.</para>"
144 "<para>Please set it in the KDE System Settings or in the <application>KAlarm</application> Configuration dialog.</para>"));
145 break;
146 case Preferences::MAIL_FROM_ADDR:
147 default:
148 errmsgs = errors(i18nc("@info", "<para>No 'From' email address is configured.</para>"
149 "<para>Please set it in the <application>KAlarm</application> Configuration dialog.</para>"));
150 break;
152 return -1;
154 jobdata.bcc = (jobdata.event.emailBcc() ? Preferences::emailBccAddress() : QString());
155 kDebug() << "To:" << jobdata.event.emailAddresses(",")
156 << endl << "Subject:" << jobdata.event.emailSubject();
158 MailTransport::TransportManager* manager = MailTransport::TransportManager::self();
159 MailTransport::Transport* transport = 0;
160 if (Preferences::emailClient() == Preferences::sendmail)
162 kDebug() << "Sending via sendmail";
163 const QList<MailTransport::Transport*> transports = manager->transports();
164 for (int i = 0, count = transports.count(); i < count; ++i)
166 if (transports[i]->type() == MailTransport::Transport::EnumType::Sendmail)
168 // Use the first sendmail transport found
169 transport = transports[i];
170 break;
173 if (!transport)
175 QString command = KStandardDirs::findExe(QLatin1String("sendmail"),
176 QLatin1String("/sbin:/usr/sbin:/usr/lib"));
177 transport = manager->createTransport();
178 transport->setName(QLatin1String("sendmail"));
179 transport->setType(MailTransport::Transport::EnumType::Sendmail);
180 transport->setHost(command);
181 transport->setRequiresAuthentication(false);
182 transport->setStorePassword(false);
183 manager->addTransport(transport);
184 transport->writeConfig();
185 kDebug() << "Creating sendmail transport, id=" << transport->id();
188 else
190 kDebug() << "Sending via KDE";
191 const int transportId = identity.transport().isEmpty() ? -1 : identity.transport().toInt();
192 transport = manager->transportById( transportId, true );
193 if (!transport)
195 kError() << "No mail transport found for identity" << identity.identityName() << "uoid" << identity.uoid();
196 errmsgs = errors(i18nc("@info", "No mail transport configured for email identity <resource>%1</resource>", identity.identityName()));
197 return -1;
200 kDebug() << "Using transport" << transport->name() << ", id=" << transport->id();
202 KMime::Message::Ptr message = KMime::Message::Ptr(new KMime::Message);
203 initHeaders(*message, jobdata);
204 err = appendBodyAttachments(*message, jobdata);
205 if (!err.isNull())
207 kError() << "Error compiling message:" << err;
208 errmsgs = errors(err);
209 return -1;
212 MailTransport::MessageQueueJob* mailjob = new MailTransport::MessageQueueJob(kapp);
213 mailjob->setMessage(message);
214 mailjob->transportAttribute().setTransportId(transport->id());
215 mailjob->addressAttribute().setFrom(jobdata.from);
216 mailjob->addressAttribute().setTo(static_cast<QStringList>(jobdata.event.emailAddresses()));
217 if (!jobdata.bcc.isEmpty())
218 mailjob->addressAttribute().setBcc(QStringList(KPIMUtils::extractEmailAddress(jobdata.bcc)));
219 MailTransport::SentBehaviourAttribute::SentBehaviour sentAction =
220 (Preferences::emailClient() == Preferences::kmail || Preferences::emailCopyToKMail())
221 ? MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection : MailTransport::SentBehaviourAttribute::Delete;
222 mailjob->sentBehaviourAttribute().setSentBehaviour(sentAction);
223 mJobs.enqueue(mailjob);
224 mJobData.enqueue(jobdata);
225 if (mJobs.count() == 1)
227 // There are no jobs already active or queued, so send now
228 connect(mailjob, SIGNAL(result(KJob*)), instance(), SLOT(slotEmailSent(KJob*)));
229 mailjob->start();
231 return 0;
234 /******************************************************************************
235 * Called when sending an email is complete.
237 void KAMail::slotEmailSent(KJob* job)
239 bool fail = false;
240 bool copyerr = false;
241 QStringList errmsgs;
242 if (job->error())
244 kError() << "Failed:" << job->errorString();
245 errmsgs = errors(job->errorString(), SEND_ERROR);
246 fail = true;
248 JobData jobdata;
249 if (mJobs.isEmpty() || mJobData.isEmpty() || job != mJobs.head())
251 // The queue has been corrupted, so we can't locate the job's data
252 kError() << "Wrong job at head of queue: wiping queue";
253 mJobs.clear();
254 mJobData.clear();
255 if (!errmsgs.isEmpty())
256 theApp()->emailSent(jobdata, errmsgs);
257 errmsgs.clear();
258 errmsgs += i18nc("@info", "Emails may not have been sent");
259 errmsgs += i18nc("@info", "Program error");
260 theApp()->emailSent(jobdata, errmsgs);
261 return;
263 mJobs.dequeue();
264 jobdata = mJobData.dequeue();
265 if (jobdata.allowNotify)
266 notifyQueued(jobdata.event);
267 theApp()->emailSent(jobdata, errmsgs, copyerr);
268 if (!mJobs.isEmpty())
270 // Send the next queued email
271 connect(mJobs.head(), SIGNAL(result(KJob*)), instance(), SLOT(slotEmailSent(KJob*)));
272 mJobs.head()->start();
276 /******************************************************************************
277 * Create the headers part of the email.
279 void initHeaders(KMime::Message& message, KAMail::JobData& data)
281 KMime::Headers::Date* date = new KMime::Headers::Date;
282 date->setDateTime(KDateTime::currentDateTime(Preferences::timeZone()));
283 message.setHeader(date);
285 KMime::Headers::From* from = new KMime::Headers::From;
286 from->fromUnicodeString(data.from, autoDetectCharset(data.from));
287 message.setHeader(from);
289 KMime::Headers::To* to = new KMime::Headers::To;
290 EmailAddressList toList = data.event.emailAddresses();
291 for (int i = 0, count = toList.count(); i < count; ++i)
292 #ifdef USE_AKONADI
293 to->addAddress(toList[i]->email().toLatin1(), toList[i]->name());
294 #else
295 to->addAddress(toList[i].email().toLatin1(), toList[i].name());
296 #endif
297 message.setHeader(to);
299 if (!data.bcc.isEmpty())
301 KMime::Headers::Bcc* bcc = new KMime::Headers::Bcc;
302 bcc->fromUnicodeString(data.bcc, autoDetectCharset(data.bcc));
303 message.setHeader(bcc);
306 KMime::Headers::Subject* subject = new KMime::Headers::Subject;
307 QString str = data.event.emailSubject();
308 subject->fromUnicodeString(str, autoDetectCharset(str));
309 message.setHeader(subject);
311 KMime::Headers::UserAgent* agent = new KMime::Headers::UserAgent;
312 agent->fromUnicodeString(KGlobal::mainComponent().aboutData()->programName() + "/" KALARM_VERSION, "us-ascii");
313 message.setHeader(agent);
315 KMime::Headers::MessageID* id = new KMime::Headers::MessageID;
316 id->generate(data.from.mid(data.from.indexOf('@') + 1).toLatin1());
317 message.setHeader(id);
320 /******************************************************************************
321 * Append the body and attachments to the email text.
322 * Reply = reason for error
323 * = empty string if successful.
325 QString KAMail::appendBodyAttachments(KMime::Message& message, JobData& data)
327 QStringList attachments = data.event.emailAttachments();
328 if (!attachments.count())
330 // There are no attachments, so simply append the message body
331 message.contentType()->setMimeType("text/plain");
332 message.contentType()->setCharset("utf-8");
333 message.fromUnicodeString(data.event.message());
334 QList<KMime::Headers::contentEncoding> encodings = KMime::encodingsForData(message.body());
335 encodings.removeAll(KMime::Headers::CE8Bit); // not handled by KMime
336 message.contentTransferEncoding()->setEncoding(encodings[0]);
337 message.assemble();
339 else
341 // There are attachments, so the message must be in MIME format
342 message.contentType()->setMimeType("multipart/mixed");
343 message.contentType()->setBoundary(KMime::multiPartBoundary());
345 if (!data.event.message().isEmpty())
347 // There is a message body
348 KMime::Content* content = new KMime::Content();
349 content->contentType()->setMimeType("text/plain");
350 content->contentType()->setCharset("utf-8");
351 content->fromUnicodeString(data.event.message());
352 QList<KMime::Headers::contentEncoding> encodings = KMime::encodingsForData(content->body());
353 encodings.removeAll(KMime::Headers::CE8Bit); // not handled by KMime
354 content->contentTransferEncoding()->setEncoding(encodings[0]);
355 content->assemble();
356 message.addContent(content);
359 // Append each attachment in turn
360 for (QStringList::Iterator at = attachments.begin(); at != attachments.end(); ++at)
362 QString attachment = (*at).toLocal8Bit();
363 KUrl url(attachment);
364 QString attachError = i18nc("@info", "Error attaching file: <filename>%1</filename>", attachment);
365 url.cleanPath();
366 KIO::UDSEntry uds;
367 if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow())) {
368 kError() << "Not found:" << attachment;
369 return i18nc("@info", "Attachment not found: <filename>%1</filename>", attachment);
371 KFileItem fi(uds, url);
372 if (fi.isDir() || !fi.isReadable()) {
373 kError() << "Not file/not readable:" << attachment;
374 return attachError;
377 // Read the file contents
378 QString tmpFile;
379 if (!KIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow())) {
380 kError() << "Load failure:" << attachment;
381 return attachError;
383 QFile file(tmpFile);
384 if (!file.open(QIODevice::ReadOnly)) {
385 kDebug() << "tmp load error:" << attachment;
386 return attachError;
388 qint64 size = file.size();
389 QByteArray contents = file.readAll();
390 file.close();
391 bool atterror = false;
392 if (contents.size() < size) {
393 kDebug() << "Read error:" << attachment;
394 atterror = true;
397 QByteArray coded = KCodecs::base64Encode(contents, true);
398 KMime::Content* content = new KMime::Content();
399 content = new KMime::Content();
400 content->setBody(coded + "\n\n");
402 // Set the content type
403 KMimeType::Ptr type = KMimeType::findByUrl(url);
404 KMime::Headers::ContentType* ctype = new KMime::Headers::ContentType(content);
405 ctype->fromUnicodeString(type->name(), autoDetectCharset(type->name()));
406 ctype->setName(attachment, "local");
407 content->setHeader(ctype);
409 // Set the encoding
410 KMime::Headers::ContentTransferEncoding* cte = new KMime::Headers::ContentTransferEncoding(content);
411 cte->setEncoding(KMime::Headers::CEbase64);
412 cte->setDecoded(false);
413 content->setHeader(cte);
414 content->assemble();
415 message.addContent(content);
416 if (atterror)
417 return attachError;
419 message.assemble();
421 return QString();
424 /******************************************************************************
425 * If any of the destination email addresses are non-local, display a
426 * notification message saying that an email has been queued for sending.
428 void KAMail::notifyQueued(const KAEvent& event)
430 KMime::Types::Address addr;
431 QString localhost = QLatin1String("localhost");
432 QString hostname = QHostInfo::localHostName();
433 const EmailAddressList& addresses = event.emailAddresses();
434 for (int i = 0, end = addresses.count(); i < end; ++i)
436 #ifdef USE_AKONADI
437 QByteArray email = addresses[i]->email().toLocal8Bit();
438 #else
439 QByteArray email = addresses[i].email().toLocal8Bit();
440 #endif
441 const char* em = email;
442 if (!email.isEmpty()
443 && HeaderParsing::parseAddress(em, em + email.length(), addr))
445 QString domain = addr.mailboxList.first().addrSpec().domain;
446 if (!domain.isEmpty() && domain != localhost && domain != hostname)
448 KAMessageBox::information(MainWindow::mainMainWindow(), i18nc("@info", "An email has been queued to be sent"), QString(), Preferences::EMAIL_QUEUED_NOTIFY);
449 return;
455 /******************************************************************************
456 * Fetch the user's email address configured in the KDE System Settings.
458 QString KAMail::controlCentreAddress()
460 KEMailSettings e;
461 return e.getSetting(KEMailSettings::EmailAddress);
464 /******************************************************************************
465 * Parse a list of email addresses, optionally containing display names,
466 * entered by the user.
467 * Reply = the invalid item if error, else empty string.
469 QString KAMail::convertAddresses(const QString& items, EmailAddressList& list)
471 list.clear();
472 QString invalidItem;
473 const KMime::Types::Mailbox::List mailboxes = parseAddresses(items, invalidItem);
474 if (!invalidItem.isEmpty())
475 return invalidItem;
476 for (int i = 0, count = mailboxes.count(); i < count; ++i)
478 #ifdef USE_AKONADI
479 KCalCore::Person::Ptr person(new KCalCore::Person(mailboxes[i].name(), mailboxes[i].addrSpec().asString()));
480 list += person;
481 #else
482 list += KCal::Person(mailboxes[i].name(), mailboxes[i].addrSpec().asString());
483 #endif
485 return QString();
488 /******************************************************************************
489 * Check the validity of an email address.
490 * Because internal email addresses don't have to abide by the usual internet
491 * email address rules, only some basic checks are made.
492 * Reply = 1 if alright, 0 if empty, -1 if error.
494 int KAMail::checkAddress(QString& address)
496 address = address.trimmed();
497 // Check that there are no list separator characters present
498 if (address.indexOf(QLatin1Char(',')) >= 0 || address.indexOf(QLatin1Char(';')) >= 0)
499 return -1;
500 int n = address.length();
501 if (!n)
502 return 0;
503 int start = 0;
504 int end = n - 1;
505 if (address[end] == QLatin1Char('>'))
507 // The email address is in <...>
508 if ((start = address.indexOf(QLatin1Char('<'))) < 0)
509 return -1;
510 ++start;
511 --end;
513 int i = address.indexOf(QLatin1Char('@'), start);
514 if (i >= 0)
516 if (i == start || i == end) // check @ isn't the first or last character
517 // || address.indexOf(QLatin1Char('@'), i + 1) >= 0) // check for multiple @ characters
518 return -1;
520 /* else
522 // Allow the @ character to be missing if it's a local user
523 if (!getpwnam(address.mid(start, end - start + 1).toLocal8Bit()))
524 return false;
526 for (int i = start; i <= end; ++i)
528 char ch = address[i].toLatin1();
529 if (ch == '.' || ch == '@' || ch == '-' || ch == '_'
530 || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
531 || (ch >= '0' && ch <= '9'))
532 continue;
533 return false;
535 return 1;
538 /******************************************************************************
539 * Convert a comma or semicolon delimited list of attachments into a
540 * QStringList. The items are checked for validity.
541 * Reply = the invalid item if error, else empty string.
543 QString KAMail::convertAttachments(const QString& items, QStringList& list)
545 KUrl url;
546 list.clear();
547 int length = items.length();
548 for (int next = 0; next < length; )
550 // Find the first delimiter character (, or ;)
551 int i = items.indexOf(QLatin1Char(','), next);
552 if (i < 0)
553 i = items.length();
554 int sc = items.indexOf(QLatin1Char(';'), next);
555 if (sc < 0)
556 sc = items.length();
557 if (sc < i)
558 i = sc;
559 QString item = items.mid(next, i - next).trimmed();
560 switch (checkAttachment(item))
562 case 1: list += item; break;
563 case 0: break; // empty attachment name
564 case -1:
565 default: return item; // error
567 next = i + 1;
569 return QString();
572 /******************************************************************************
573 * Check for the existence of the attachment file.
574 * If non-null, '*url' receives the KUrl of the attachment.
575 * Reply = 1 if attachment exists
576 * = 0 if null name
577 * = -1 if doesn't exist.
579 int KAMail::checkAttachment(QString& attachment, KUrl* url)
581 attachment = attachment.trimmed();
582 if (attachment.isEmpty())
584 if (url)
585 *url = KUrl();
586 return 0;
588 // Check that the file exists
589 KUrl u(attachment);
590 u.cleanPath();
591 if (url)
592 *url = u;
593 return checkAttachment(u) ? 1 : -1;
596 /******************************************************************************
597 * Check for the existence of the attachment file.
599 bool KAMail::checkAttachment(const KUrl& url)
601 KIO::UDSEntry uds;
602 if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow()))
603 return false; // doesn't exist
604 KFileItem fi(uds, url);
605 if (fi.isDir() || !fi.isReadable())
606 return false;
607 return true;
610 /******************************************************************************
611 * Set the appropriate error messages for a given error string.
613 QStringList KAMail::errors(const QString& err, ErrType prefix)
615 QString error1;
616 switch (prefix)
618 case SEND_FAIL: error1 = i18nc("@info", "Failed to send email"); break;
619 case SEND_ERROR: error1 = i18nc("@info", "Error sending email"); break;
621 if (err.isEmpty())
622 return QStringList(error1);
623 QStringList errs(QString::fromLatin1("%1:").arg(error1));
624 errs += err;
625 return errs;
628 #ifdef KMAIL_SUPPORTED
629 /******************************************************************************
630 * Get the body of an email from KMail, given its serial number.
632 QString KAMail::getMailBody(quint32 serialNumber)
634 QList<QVariant> args;
635 args << serialNumber << (int)0;
636 #ifdef __GNUC__
637 #warning Set correct DBus interface/object for kmail
638 #endif
639 QDBusInterface iface(KMAIL_DBUS_SERVICE, QString(), QLatin1String("KMailIface"));
640 QDBusReply<QString> reply = iface.callWithArgumentList(QDBus::Block, QLatin1String("getDecodedBodyPart"), args);
641 if (!reply.isValid())
643 kError() << "D-Bus call failed:" << reply.error().message();
644 return QString();
646 return reply.value();
648 #endif
650 //-----------------------------------------------------------------------------
651 // Based on KMail KMMsgBase::autoDetectCharset().
652 QByteArray autoDetectCharset(const QString& text)
654 static QList<QByteArray> charsets;
655 if (charsets.isEmpty())
656 charsets << "us-ascii" << "iso-8859-1" << "locale" << "utf-8";
658 for (int i = 0, count = charsets.count(); i < count; ++i)
660 QByteArray encoding = charsets[i];
661 if (encoding == "locale")
663 encoding = QTextCodec::codecForName(KGlobal::locale()->encoding())->name();
664 kAsciiToLower(encoding.data());
666 if (text.isEmpty())
667 return encoding;
668 if (encoding == "us-ascii")
670 if (KMime::isUsAscii(text))
671 return encoding;
673 else
675 const QTextCodec *codec = codecForName(encoding);
676 if (!codec)
677 kDebug() <<"Auto-Charset: Something is wrong and I can not get a codec. [" << encoding <<"]";
678 else
680 if (codec->canEncode(text))
681 return encoding;
685 return 0;
688 //-----------------------------------------------------------------------------
689 // Based on KMail KMMsgBase::codecForName().
690 const QTextCodec* codecForName(const QByteArray& str)
692 if (str.isEmpty())
693 return 0;
694 QByteArray codec = str;
695 kAsciiToLower(codec.data());
696 return KGlobal::charsets()->codecForName(codec);
699 /******************************************************************************
700 * Parse a string containing multiple addresses, separated by comma or semicolon,
701 * while retaining Unicode name parts.
702 * Note that this only needs to parse strings input into KAlarm, so it only
703 * needs to accept the common syntax for email addresses, not obsolete syntax.
705 KMime::Types::Mailbox::List parseAddresses(const QString& text, QString& invalidItem)
707 KMime::Types::Mailbox::List list;
708 int state = 0;
709 int start = 0; // start of this item
710 int endName = 0; // character after end of name
711 int startAddr = 0; // start of address
712 int endAddr = 0; // character after end of address
713 char lastch = '\0';
714 bool ended = false; // found the end of the item
715 for (int i = 0, count = text.length(); i <= count; ++i)
717 if (i == count)
718 ended = true;
719 else
721 char ch = text[i].toLatin1();
722 switch (state)
724 case 0: // looking for start of item
725 if (ch == ' ' || ch == '\t')
726 continue;
727 start = i;
728 state = (ch == '"') ? 10 : 1;
729 break;
730 case 1: // looking for start of address, or end of item
731 switch (ch)
733 case '<':
734 startAddr = i + 1;
735 state = 2;
736 break;
737 case ',':
738 case ';':
739 ended = true;
740 break;
741 case ' ':
742 break;
743 default:
744 endName = i + 1;
745 break;
747 break;
748 case 2: // looking for '>' at end of address
749 if (ch == '>')
751 endAddr = i;
752 state = 3;
754 break;
755 case 3: // looking for item separator
756 if (ch == ',' || ch == ';')
757 ended = true;
758 else if (ch != ' ')
760 invalidItem = text.mid(start);
761 return KMime::Types::Mailbox::List();
763 break;
764 case 10: // looking for closing quote
765 if (ch == '"' && lastch != '\\')
767 ++start; // remove opening quote from name
768 endName = i;
769 state = 11;
771 lastch = ch;
772 break;
773 case 11: // looking for '<'
774 if (ch == '<')
776 startAddr = i + 1;
777 state = 2;
779 break;
782 if (ended)
784 // Found the end of the item - add it to the list
785 if (!startAddr)
787 startAddr = start;
788 endAddr = endName;
789 endName = 0;
791 QString addr = text.mid(startAddr, endAddr - startAddr);
792 KMime::Types::Mailbox mbox;
793 mbox.fromUnicodeString(addr);
794 if (mbox.address().isEmpty())
796 invalidItem = text.mid(start, endAddr - start);
797 return KMime::Types::Mailbox::List();
799 if (endName)
801 int len = endName - start;
802 QString name = text.mid(start, endName - start);
803 if (name[0] == '"' && name[len - 1] == '"')
804 name = name.mid(1, len - 2);
805 mbox.setName(name);
807 list.append(mbox);
809 endName = startAddr = endAddr = 0;
810 start = i + 1;
811 state = 0;
812 ended = false;
815 return list;
818 /*=============================================================================
819 = HeaderParsing : modified and additional functions.
820 = The following functions are modified from, or additional to, those in
821 = libkmime kmime_header_parsing.cpp.
822 =============================================================================*/
824 namespace HeaderParsing
827 using namespace KMime;
828 using namespace KMime::Types;
829 using namespace KMime::HeaderParsing;
831 /******************************************************************************
832 * New function.
833 * Allow a local user name to be specified as an email address.
835 bool parseUserName( const char* & scursor, const char * const send,
836 QString & result, bool isCRLF ) {
838 QString maybeLocalPart;
839 QString tmp;
841 if ( scursor != send ) {
842 // first, eat any whitespace
843 eatCFWS( scursor, send, isCRLF );
845 char ch = *scursor++;
846 switch ( ch ) {
847 case '.': // dot
848 case '@':
849 case '"': // quoted-string
850 return false;
852 default: // atom
853 scursor--; // re-set scursor to point to ch again
854 tmp.clear();
855 if ( parseAtom( scursor, send, result, false /* no 8bit */ ) ) {
856 if (getpwnam(result.toLocal8Bit()))
857 return true;
859 return false; // parseAtom can only fail if the first char is non-atext.
862 return false;
865 /******************************************************************************
866 * Modified function.
867 * Allow a local user name to be specified as an email address, and reinstate
868 * the original scursor on error return.
870 bool parseAddress( const char* & scursor, const char * const send,
871 Address & result, bool isCRLF ) {
872 // address := mailbox / group
874 eatCFWS( scursor, send, isCRLF );
875 if ( scursor == send )
876 return false;
878 // first try if it's a single mailbox:
879 Mailbox maybeMailbox;
880 const char * oldscursor = scursor;
881 if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
882 // yes, it is:
883 result.displayName.clear();
884 result.mailboxList.append( maybeMailbox );
885 return true;
887 scursor = oldscursor;
889 // KAlarm: Allow a local user name to be specified
890 // no, it's not a single mailbox. Try if it's a local user name:
891 QString maybeUserName;
892 if ( parseUserName( scursor, send, maybeUserName, isCRLF ) ) {
893 // yes, it is:
894 maybeMailbox.setName( QString() );
895 AddrSpec addrSpec;
896 addrSpec.localPart = maybeUserName;
897 addrSpec.domain.clear();
898 maybeMailbox.setAddress( addrSpec );
899 result.displayName.clear();
900 result.mailboxList.append( maybeMailbox );
901 return true;
903 scursor = oldscursor;
905 Address maybeAddress;
907 // no, it's not a single mailbox. Try if it's a group:
908 if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) )
910 scursor = oldscursor; // KAlarm: reinstate original scursor on error return
911 return false;
914 result = maybeAddress;
915 return true;
918 } // namespace HeaderParsing
920 // vim: et sw=4: