2 * kamail.cpp - email functions
4 * Copyright © 2002-2015 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)
24 #include "functions.h"
25 #include "kalarmapp.h"
26 #include "mainwindow.h"
27 #include "messagebox.h"
28 #include "preferences.h"
30 #include <kalarmcal/identities.h>
31 #include <KIdentityManagement/kidentitymanagement/identitymanager.h>
32 #include <KIdentityManagement/kidentitymanagement/identity.h>
33 #include <MailTransport/mailtransport/transportmanager.h>
34 #include <MailTransport/mailtransport/transport.h>
35 #include <MailTransport/mailtransport/messagequeuejob.h>
36 #include <KCalCore/Person>
37 #include <kmime/kmime_header_parsing.h>
38 #include <kmime/kmime_headers.h>
39 #include <kmime/kmime_message.h>
41 #include <KEmailAddress>
44 #include <KLocalizedString>
45 #include <kfileitem.h>
46 #include <KIO/StatJob>
47 #include <KJobWidgets>
48 #include <kemailsettings.h>
50 #include <kcharsets.h>
59 #include <QStandardPaths>
60 #include <QtDBus/QtDBus>
61 #include "kalarm_debug.h"
65 #include "kmailinterface.h"
67 static const QLatin1String
KMAIL_DBUS_SERVICE("org.kde.kmail");
68 //static const QLatin1String KMAIL_DBUS_PATH("/KMail");
70 namespace HeaderParsing
72 bool parseAddress( const char* & scursor
, const char * const send
,
73 KMime::Types::Address
& result
, bool isCRLF
=false );
76 static void initHeaders(KMime::Message
&, KAMail::JobData
&);
77 static KMime::Types::Mailbox::List
parseAddresses(const QString
& text
, QString
& invalidItem
);
78 static QString
extractEmailAndNormalize(const QString
& emailAddress
);
79 static QStringList
extractEmailsAndNormalize(const QString
& emailAddresses
);
80 static QByteArray
autoDetectCharset(const QString
& text
);
81 static const QTextCodec
* codecForName(const QByteArray
& str
);
83 QString
KAMail::i18n_NeedFromEmailAddress()
84 { return i18nc("@info", "A 'From' email address must be configured in order to execute email alarms."); }
86 QString
KAMail::i18n_sent_mail()
87 { return i18nc("@info KMail folder name: this should be translated the same as in kmail", "sent-mail"); }
89 KAMail
* KAMail::mInstance
= Q_NULLPTR
; // used only to enable signals/slots to work
90 QQueue
<MailTransport::MessageQueueJob
*> KAMail::mJobs
;
91 QQueue
<KAMail::JobData
> KAMail::mJobData
;
93 KAMail
* KAMail::instance()
96 mInstance
= new KAMail();
100 /******************************************************************************
101 * Send the email message specified in an event.
102 * Reply = 1 if the message was sent - 'errmsgs' may contain copy error messages.
103 * = 0 if the message is queued for sending.
104 * = -1 if the message was not sent - 'errmsgs' contains the error messages.
106 int KAMail::send(JobData
& jobdata
, QStringList
& errmsgs
)
109 KIdentityManagement::Identity identity
;
110 if (!jobdata
.event
.emailFromId())
111 jobdata
.from
= Preferences::emailAddress();
114 identity
= Identities::identityManager()->identityForUoid(jobdata
.event
.emailFromId());
115 if (identity
.isNull())
117 qCCritical(KALARM_LOG
) << "Identity" << jobdata
.event
.emailFromId() << "not found";
118 errmsgs
= errors(xi18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> not found", jobdata
.event
.emailFromId()));
121 if (identity
.primaryEmailAddress().isEmpty())
123 qCCritical(KALARM_LOG
) << "Identity" << identity
.identityName() << "uoid" << identity
.uoid() << ": no email address";
124 errmsgs
= errors(xi18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> has no email address", identity
.identityName()));
127 jobdata
.from
= identity
.fullEmailAddr();
129 if (jobdata
.from
.isEmpty())
131 switch (Preferences::emailFrom())
133 case Preferences::MAIL_FROM_KMAIL
:
134 errmsgs
= errors(xi18nc("@info", "<para>No 'From' email address is configured (no default email identity found)</para>"
135 "<para>Please set it in <application>KMail</application> or in the <application>KAlarm</application> Configuration dialog.</para>"));
137 case Preferences::MAIL_FROM_SYS_SETTINGS
:
138 errmsgs
= errors(xi18nc("@info", "<para>No 'From' email address is configured.</para>"
139 "<para>Please set it in the KDE System Settings or in the <application>KAlarm</application> Configuration dialog.</para>"));
141 case Preferences::MAIL_FROM_ADDR
:
143 errmsgs
= errors(xi18nc("@info", "<para>No 'From' email address is configured.</para>"
144 "<para>Please set it in the <application>KAlarm</application> Configuration dialog.</para>"));
149 jobdata
.bcc
= (jobdata
.event
.emailBcc() ? Preferences::emailBccAddress() : QString());
150 qCDebug(KALARM_LOG
) << "To:" << jobdata
.event
.emailAddresses(QStringLiteral(","))
151 << endl
<< "Subject:" << jobdata
.event
.emailSubject();
153 KMime::Message::Ptr message
= KMime::Message::Ptr(new KMime::Message
);
155 MailTransport::TransportManager
* manager
= MailTransport::TransportManager::self();
156 MailTransport::Transport
* transport
= Q_NULLPTR
;
157 if (Preferences::emailClient() == Preferences::sendmail
)
159 qCDebug(KALARM_LOG
) << "Sending via sendmail";
161 paths
<< QStringLiteral("/sbin") << QStringLiteral("/usr/sbin") << QStringLiteral("/usr/lib");
162 QString command
= QStandardPaths::findExecutable(QStringLiteral("sendmail"), paths
);
163 if (!command
.isNull())
165 command
+= QStringLiteral(" -f ");
166 command
+= extractEmailAndNormalize(jobdata
.from
);
167 command
+= QStringLiteral(" -oi -t ");
168 initHeaders(*message
, jobdata
);
172 command
= QStandardPaths::findExecutable(QStringLiteral("mail"), paths
);
173 if (command
.isNull())
175 qCCritical(KALARM_LOG
) << "sendmail not found";
176 errmsgs
= errors(xi18nc("@info", "<command>%1</command> not found", QStringLiteral("sendmail"))); // give up
180 command
+= QStringLiteral(" -s ");
181 command
+= KShell::quoteArg(jobdata
.event
.emailSubject());
183 if (!jobdata
.bcc
.isEmpty())
185 command
+= QStringLiteral(" -b ");
186 command
+= extractEmailAndNormalize(jobdata
.bcc
);
189 command
+= QLatin1Char(' ');
190 command
+= jobdata
.event
.emailPureAddresses(QStringLiteral(" ")); // locally provided, okay
192 // Add the body and attachments to the message.
193 // (Sendmail requires attachments to have already been included in the message.)
194 err
= appendBodyAttachments(*message
, jobdata
);
197 qCCritical(KALARM_LOG
) << "Error compiling message:" << err
;
198 errmsgs
= errors(err
);
202 // Execute the send command
203 FILE* fd
= ::popen(command
.toLocal8Bit(), "w");
206 qCCritical(KALARM_LOG
) << "Unable to open a pipe to " << command
;
211 QByteArray encoded
= message
->encodedContent();
212 fwrite(encoded
, encoded
.length(), 1, fd
);
215 #ifdef KMAIL_SUPPORTED
216 if (Preferences::emailCopyToKMail())
218 // Create a copy of the sent email in KMail's 'sent-mail' folder,
219 // or if there was a send error, in KMail's 'outbox' folder.
220 err
= addToKMailFolder(jobdata
, "sent-mail", true);
222 errmsgs
+= errors(err
, COPY_ERROR
); // not a fatal error - continue
226 if (jobdata
.allowNotify
)
227 notifyQueued(jobdata
.event
);
232 qCDebug(KALARM_LOG
) << "Sending via KDE";
233 const int transportId
= identity
.transport().isEmpty() ? -1 : identity
.transport().toInt();
234 transport
= manager
->transportById(transportId
, true);
237 qCCritical(KALARM_LOG
) << "No mail transport found for identity" << identity
.identityName() << "uoid" << identity
.uoid();
238 errmsgs
= errors(xi18nc("@info", "No mail transport configured for email identity <resource>%1</resource>", identity
.identityName()));
241 qCDebug(KALARM_LOG
) << "Using transport" << transport
->name() << ", id=" << transport
->id();
243 initHeaders(*message
, jobdata
);
244 err
= appendBodyAttachments(*message
, jobdata
);
247 qCCritical(KALARM_LOG
) << "Error compiling message:" << err
;
248 errmsgs
= errors(err
);
252 MailTransport::MessageQueueJob
* mailjob
= new MailTransport::MessageQueueJob(qApp
);
253 mailjob
->setMessage(message
);
254 mailjob
->transportAttribute().setTransportId(transport
->id());
255 // MessageQueueJob email addresses must be pure, i.e. without display name. Note
256 // that display names are included in the actual headers set up by initHeaders().
257 mailjob
->addressAttribute().setFrom(extractEmailAndNormalize(jobdata
.from
));
258 mailjob
->addressAttribute().setTo(extractEmailsAndNormalize(jobdata
.event
.emailAddresses(QStringLiteral(","))));
259 if (!jobdata
.bcc
.isEmpty())
260 mailjob
->addressAttribute().setBcc(extractEmailsAndNormalize(jobdata
.bcc
));
261 MailTransport::SentBehaviourAttribute::SentBehaviour sentAction
=
262 (Preferences::emailClient() == Preferences::kmail
|| Preferences::emailCopyToKMail())
263 ? MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection
: MailTransport::SentBehaviourAttribute::Delete
;
264 mailjob
->sentBehaviourAttribute().setSentBehaviour(sentAction
);
265 mJobs
.enqueue(mailjob
);
266 mJobData
.enqueue(jobdata
);
267 if (mJobs
.count() == 1)
269 // There are no jobs already active or queued, so send now
270 connect(mailjob
, &KJob::result
, instance(), &KAMail::slotEmailSent
);
277 /******************************************************************************
278 * Called when sending an email is complete.
280 void KAMail::slotEmailSent(KJob
* job
)
282 bool copyerr
= false;
286 qCCritical(KALARM_LOG
) << "Failed:" << job
->errorString();
287 errmsgs
= errors(job
->errorString(), SEND_ERROR
);
290 if (mJobs
.isEmpty() || mJobData
.isEmpty() || job
!= mJobs
.head())
292 // The queue has been corrupted, so we can't locate the job's data
293 qCCritical(KALARM_LOG
) << "Wrong job at head of queue: wiping queue";
296 if (!errmsgs
.isEmpty())
297 theApp()->emailSent(jobdata
, errmsgs
);
299 errmsgs
+= i18nc("@info", "Emails may not have been sent");
300 errmsgs
+= i18nc("@info", "Program error");
301 theApp()->emailSent(jobdata
, errmsgs
);
305 jobdata
= mJobData
.dequeue();
306 if (jobdata
.allowNotify
)
307 notifyQueued(jobdata
.event
);
308 theApp()->emailSent(jobdata
, errmsgs
, copyerr
);
309 if (!mJobs
.isEmpty())
311 // Send the next queued email
312 connect(mJobs
.head(), &KJob::result
, instance(), &KAMail::slotEmailSent
);
313 mJobs
.head()->start();
317 /******************************************************************************
318 * Create the headers part of the email.
320 void initHeaders(KMime::Message
& message
, KAMail::JobData
& data
)
322 KMime::Headers::Date
* date
= new KMime::Headers::Date
;
324 date
->setDateTime(QDateTime::currentDateTime(/*Preferences::timeZone()*/));
325 message
.setHeader(date
);
327 KMime::Headers::From
* from
= new KMime::Headers::From
;
328 from
->fromUnicodeString(data
.from
, autoDetectCharset(data
.from
));
329 message
.setHeader(from
);
331 KMime::Headers::To
* to
= new KMime::Headers::To
;
332 KCalCore::Person::List toList
= data
.event
.emailAddressees();
333 for (int i
= 0, count
= toList
.count(); i
< count
; ++i
)
334 to
->addAddress(toList
[i
]->email().toLatin1(), toList
[i
]->name());
335 message
.setHeader(to
);
337 if (!data
.bcc
.isEmpty())
339 KMime::Headers::Bcc
* bcc
= new KMime::Headers::Bcc
;
340 bcc
->fromUnicodeString(data
.bcc
, autoDetectCharset(data
.bcc
));
341 message
.setHeader(bcc
);
344 KMime::Headers::Subject
* subject
= new KMime::Headers::Subject
;
345 QString str
= data
.event
.emailSubject();
346 subject
->fromUnicodeString(str
, autoDetectCharset(str
));
347 message
.setHeader(subject
);
349 KMime::Headers::UserAgent
* agent
= new KMime::Headers::UserAgent
;
350 agent
->fromUnicodeString(KAboutData::applicationData().displayName() + QLatin1String("/" KALARM_VERSION
), "us-ascii");
351 message
.setHeader(agent
);
353 KMime::Headers::MessageID
* id
= new KMime::Headers::MessageID
;
354 id
->generate(data
.from
.mid(data
.from
.indexOf(QLatin1Char('@')) + 1).toLatin1());
355 message
.setHeader(id
);
358 /******************************************************************************
359 * Append the body and attachments to the email text.
360 * Reply = reason for error
361 * = empty string if successful.
363 QString
KAMail::appendBodyAttachments(KMime::Message
& message
, JobData
& data
)
365 QStringList attachments
= data
.event
.emailAttachments();
366 if (!attachments
.count())
368 // There are no attachments, so simply append the message body
369 message
.contentType()->setMimeType("text/plain");
370 message
.contentType()->setCharset("utf-8");
371 message
.fromUnicodeString(data
.event
.message());
372 auto encodings
= KMime::encodingsForData(message
.body());
373 encodings
.removeAll(KMime::Headers::CE8Bit
); // not handled by KMime
374 message
.contentTransferEncoding()->setEncoding(encodings
[0]);
379 // There are attachments, so the message must be in MIME format
380 message
.contentType()->setMimeType("multipart/mixed");
381 message
.contentType()->setBoundary(KMime::multiPartBoundary());
383 if (!data
.event
.message().isEmpty())
385 // There is a message body
386 KMime::Content
* content
= new KMime::Content();
387 content
->contentType()->setMimeType("text/plain");
388 content
->contentType()->setCharset("utf-8");
389 content
->fromUnicodeString(data
.event
.message());
390 auto encodings
= KMime::encodingsForData(content
->body());
391 encodings
.removeAll(KMime::Headers::CE8Bit
); // not handled by KMime
392 content
->contentTransferEncoding()->setEncoding(encodings
[0]);
394 message
.addContent(content
);
397 // Append each attachment in turn
398 for (QStringList::Iterator at
= attachments
.begin(); at
!= attachments
.end(); ++at
)
400 QString attachment
= QString::fromLatin1((*at
).toLocal8Bit());
401 QUrl url
= QUrl::fromUserInput(attachment
, QString(), QUrl::AssumeLocalFile
);
402 QString attachError
= xi18nc("@info", "Error attaching file: <filename>%1</filename>", attachment
);
404 bool atterror
= false;
405 if (!url
.isLocalFile())
408 auto statJob
= KIO::stat(url
, KIO::StatJob::SourceSide
, 2);
409 KJobWidgets::setWindow(statJob
, MainWindow::mainMainWindow());
410 if (!statJob
->exec())
412 qCCritical(KALARM_LOG
) << "Not found:" << attachment
;
413 return xi18nc("@info", "Attachment not found: <filename>%1</filename>", attachment
);
415 KFileItem
fi(statJob
->statResult(), url
);
416 if (fi
.isDir() || !fi
.isReadable())
418 qCCritical(KALARM_LOG
) << "Not file/not readable:" << attachment
;
422 // Read the file contents
423 auto downloadJob
= KIO::storedGet(url
.url());
424 KJobWidgets::setWindow(downloadJob
, MainWindow::mainMainWindow());
425 if (!downloadJob
->exec())
427 qCCritical(KALARM_LOG
) << "Load failure:" << attachment
;
430 contents
= downloadJob
->data();
431 if (static_cast<unsigned>(contents
.size()) < fi
.size())
433 qCDebug(KALARM_LOG
) << "Read error:" << attachment
;
439 QFile
f(url
.toLocalFile());
440 if (!f
.open(QIODevice::ReadOnly
))
442 qCCritical(KALARM_LOG
) << "Load failure:" << attachment
;
445 contents
= f
.readAll();
448 QByteArray coded
= KCodecs::base64Encode(contents
, true);
449 KMime::Content
* content
= new KMime::Content();
450 content
->setBody(coded
+ "\n\n");
452 // Set the content type
453 QMimeDatabase mimeDb
;
454 QString typeName
= mimeDb
.mimeTypeForUrl(url
).name();
455 KMime::Headers::ContentType
* ctype
= new KMime::Headers::ContentType
;
456 ctype
->fromUnicodeString(typeName
, autoDetectCharset(typeName
));
457 ctype
->setName(attachment
, "local");
458 content
->setHeader(ctype
);
461 KMime::Headers::ContentTransferEncoding
* cte
= new KMime::Headers::ContentTransferEncoding
;
462 cte
->setEncoding(KMime::Headers::CEbase64
);
463 cte
->setDecoded(false);
464 content
->setHeader(cte
);
466 message
.addContent(content
);
475 /******************************************************************************
476 * If any of the destination email addresses are non-local, display a
477 * notification message saying that an email has been queued for sending.
479 void KAMail::notifyQueued(const KAEvent
& event
)
481 KMime::Types::Address addr
;
482 const QString localhost
= QStringLiteral("localhost");
483 const QString hostname
= QHostInfo::localHostName();
484 KCalCore::Person::List addresses
= event
.emailAddressees();
485 for (int i
= 0, end
= addresses
.count(); i
< end
; ++i
)
487 QByteArray email
= addresses
[i
]->email().toLocal8Bit();
488 const char* em
= email
;
490 && HeaderParsing::parseAddress(em
, em
+ email
.length(), addr
))
492 QString domain
= addr
.mailboxList
.at(0).addrSpec().domain
;
493 if (!domain
.isEmpty() && domain
!= localhost
&& domain
!= hostname
)
495 KAMessageBox::information(MainWindow::mainMainWindow(), i18nc("@info", "An email has been queued to be sent"), QString(), Preferences::EMAIL_QUEUED_NOTIFY
);
502 /******************************************************************************
503 * Fetch the user's email address configured in the KDE System Settings.
505 QString
KAMail::controlCentreAddress()
508 return e
.getSetting(KEMailSettings::EmailAddress
);
511 /******************************************************************************
512 * Parse a list of email addresses, optionally containing display names,
513 * entered by the user.
514 * Reply = the invalid item if error, else empty string.
516 QString
KAMail::convertAddresses(const QString
& items
, KCalCore::Person::List
& list
)
520 const KMime::Types::Mailbox::List mailboxes
= parseAddresses(items
, invalidItem
);
521 if (!invalidItem
.isEmpty())
523 for (int i
= 0, count
= mailboxes
.count(); i
< count
; ++i
)
525 KCalCore::Person::Ptr
person(new KCalCore::Person(mailboxes
[i
].name(), mailboxes
[i
].addrSpec().asString()));
531 /******************************************************************************
532 * Check the validity of an email address.
533 * Because internal email addresses don't have to abide by the usual internet
534 * email address rules, only some basic checks are made.
535 * Reply = 1 if alright, 0 if empty, -1 if error.
537 int KAMail::checkAddress(QString
& address
)
539 address
= address
.trimmed();
540 // Check that there are no list separator characters present
541 if (address
.indexOf(QLatin1Char(',')) >= 0 || address
.indexOf(QLatin1Char(';')) >= 0)
543 int n
= address
.length();
548 if (address
[end
] == QLatin1Char('>'))
550 // The email address is in <...>
551 if ((start
= address
.indexOf(QLatin1Char('<'))) < 0)
556 int i
= address
.indexOf(QLatin1Char('@'), start
);
559 if (i
== start
|| i
== end
) // check @ isn't the first or last character
560 // || address.indexOf(QLatin1Char('@'), i + 1) >= 0) // check for multiple @ characters
565 // Allow the @ character to be missing if it's a local user
566 if (!getpwnam(address.mid(start, end - start + 1).toLocal8Bit()))
569 for (int i = start; i <= end; ++i)
571 char ch = address[i].toLatin1();
572 if (ch == '.' || ch == '@' || ch == '-' || ch == '_'
573 || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
574 || (ch >= '0' && ch <= '9'))
581 /******************************************************************************
582 * Convert a comma or semicolon delimited list of attachments into a
583 * QStringList. The items are checked for validity.
584 * Reply = the invalid item if error, else empty string.
586 QString
KAMail::convertAttachments(const QString
& items
, QStringList
& list
)
589 int length
= items
.length();
590 for (int next
= 0; next
< length
; )
592 // Find the first delimiter character (, or ;)
593 int i
= items
.indexOf(QLatin1Char(','), next
);
596 int sc
= items
.indexOf(QLatin1Char(';'), next
);
601 QString item
= items
.mid(next
, i
- next
).trimmed();
602 switch (checkAttachment(item
))
604 case 1: list
+= item
; break;
605 case 0: break; // empty attachment name
607 default: return item
; // error
614 /******************************************************************************
615 * Check for the existence of the attachment file.
616 * If non-null, '*url' receives the QUrl of the attachment.
617 * Reply = 1 if attachment exists
619 * = -1 if doesn't exist.
621 int KAMail::checkAttachment(QString
& attachment
, QUrl
* url
)
623 attachment
= attachment
.trimmed();
624 if (attachment
.isEmpty())
630 // Check that the file exists
631 QUrl u
= QUrl::fromUserInput(attachment
, QString(), QUrl::AssumeLocalFile
);
632 u
.setPath(QDir::cleanPath(u
.path()));
635 return checkAttachment(u
) ? 1 : -1;
638 /******************************************************************************
639 * Check for the existence of the attachment file.
641 bool KAMail::checkAttachment(const QUrl
& url
)
643 auto statJob
= KIO::stat(url
);
644 KJobWidgets::setWindow(statJob
, MainWindow::mainMainWindow());
645 if (!statJob
->exec())
646 return false; // doesn't exist
647 KFileItem
fi(statJob
->statResult(), url
);
648 if (fi
.isDir() || !fi
.isReadable())
653 /******************************************************************************
654 * Set the appropriate error messages for a given error string.
656 QStringList
KAMail::errors(const QString
& err
, ErrType prefix
)
661 case SEND_FAIL
: error1
= i18nc("@info", "Failed to send email"); break;
662 case SEND_ERROR
: error1
= i18nc("@info", "Error sending email"); break;
663 #ifdef KMAIL_SUPPORTED
664 case COPY_ERROR
: error1
= i18nc("@info", "Error copying sent email to <application>KMail</application> <resource>%1</resource> folder", i18n_sent_mail()); break;
668 return QStringList(error1
);
669 QStringList
errs(QStringLiteral("%1:").arg(error1
));
674 /******************************************************************************
675 * Get the body of an email from KMail, given its serial number.
677 QString
KAMail::getMailBody(quint32 serialNumber
)
679 //TODO: Need to use Akonadi instead
680 QList
<QVariant
> args
;
681 args
<< serialNumber
<< (int)0;
682 QDBusInterface
iface(KMAIL_DBUS_SERVICE
, QString(), QStringLiteral("KMailIface"));
683 QDBusReply
<QString
> reply
= iface
.callWithArgumentList(QDBus::Block
, QStringLiteral("getDecodedBodyPart"), args
);
684 if (!reply
.isValid())
686 qCCritical(KALARM_LOG
) << "D-Bus call failed:" << reply
.error().message();
689 return reply
.value();
692 /******************************************************************************
693 * Extract the pure addresses from given email addresses.
695 QString
extractEmailAndNormalize(const QString
& emailAddress
)
697 return KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(emailAddress
));
700 QStringList
extractEmailsAndNormalize(const QString
& emailAddresses
)
702 const QStringList
splitEmails(KEmailAddress::splitAddressList(emailAddresses
));
703 QStringList normalizedEmail
;
704 Q_FOREACH(const QString
& email
, splitEmails
)
706 normalizedEmail
<< KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(email
));
708 return normalizedEmail
;
711 //-----------------------------------------------------------------------------
712 // Based on KMail KMMsgBase::autoDetectCharset().
713 QByteArray
autoDetectCharset(const QString
& text
)
715 static QList
<QByteArray
> charsets
;
716 if (charsets
.isEmpty())
717 charsets
<< "us-ascii" << "iso-8859-1" << "locale" << "utf-8";
719 for (int i
= 0, count
= charsets
.count(); i
< count
; ++i
)
721 QByteArray encoding
= charsets
[i
];
722 if (encoding
== "locale")
724 encoding
= QTextCodec::codecForName(KLocale::global()->encoding())->name();
725 encoding
= encoding
.toLower();
729 if (encoding
== "us-ascii")
731 if (KMime::isUsAscii(text
))
736 const QTextCodec
*codec
= codecForName(encoding
);
738 qCDebug(KALARM_LOG
) <<"Auto-Charset: Something is wrong and I cannot get a codec. [" << encoding
<<"]";
741 if (codec
->canEncode(text
))
749 //-----------------------------------------------------------------------------
750 // Based on KMail KMMsgBase::codecForName().
751 const QTextCodec
* codecForName(const QByteArray
& str
)
755 QByteArray codec
= str
.toLower();
756 return KCharsets::charsets()->codecForName(QLatin1String(codec
));
759 /******************************************************************************
760 * Parse a string containing multiple addresses, separated by comma or semicolon,
761 * while retaining Unicode name parts.
762 * Note that this only needs to parse strings input into KAlarm, so it only
763 * needs to accept the common syntax for email addresses, not obsolete syntax.
765 KMime::Types::Mailbox::List
parseAddresses(const QString
& text
, QString
& invalidItem
)
767 KMime::Types::Mailbox::List list
;
769 int start
= 0; // start of this item
770 int endName
= 0; // character after end of name
771 int startAddr
= 0; // start of address
772 int endAddr
= 0; // character after end of address
774 bool ended
= false; // found the end of the item
775 for (int i
= 0, count
= text
.length(); i
<= count
; ++i
)
781 char ch
= text
[i
].toLatin1();
784 case 0: // looking for start of item
785 if (ch
== ' ' || ch
== '\t')
788 state
= (ch
== '"') ? 10 : 1;
790 case 1: // looking for start of address, or end of item
808 case 2: // looking for '>' at end of address
815 case 3: // looking for item separator
816 if (ch
== ',' || ch
== ';')
820 invalidItem
= text
.mid(start
);
821 return KMime::Types::Mailbox::List();
824 case 10: // looking for closing quote
825 if (ch
== '"' && lastch
!= '\\')
827 ++start
; // remove opening quote from name
833 case 11: // looking for '<'
844 // Found the end of the item - add it to the list
851 QString addr
= text
.mid(startAddr
, endAddr
- startAddr
);
852 KMime::Types::Mailbox mbox
;
853 mbox
.fromUnicodeString(addr
);
854 if (mbox
.address().isEmpty())
856 invalidItem
= text
.mid(start
, endAddr
- start
);
857 return KMime::Types::Mailbox::List();
861 int len
= endName
- start
;
862 QString name
= text
.mid(start
, endName
- start
);
863 if (name
[0] == QLatin1Char('"') && name
[len
- 1] == QLatin1Char('"'))
864 name
= name
.mid(1, len
- 2);
869 endName
= startAddr
= endAddr
= 0;
878 /*=============================================================================
879 = HeaderParsing : modified and additional functions.
880 = The following functions are modified from, or additional to, those in
881 = libkmime kmime_header_parsing.cpp.
882 =============================================================================*/
884 namespace HeaderParsing
887 using namespace KMime
;
888 using namespace KMime::Types
;
889 using namespace KMime::HeaderParsing
;
891 /******************************************************************************
893 * Allow a local user name to be specified as an email address.
895 bool parseUserName( const char* & scursor
, const char * const send
,
896 QString
& result
, bool isCRLF
) {
898 QString maybeLocalPart
;
901 if ( scursor
!= send
) {
902 // first, eat any whitespace
903 eatCFWS( scursor
, send
, isCRLF
);
905 char ch
= *scursor
++;
909 case '"': // quoted-string
913 scursor
--; // re-set scursor to point to ch again
915 if ( parseAtom( scursor
, send
, result
, false /* no 8bit */ ) ) {
916 if (getpwnam(result
.toLocal8Bit()))
919 return false; // parseAtom can only fail if the first char is non-atext.
925 /******************************************************************************
927 * Allow a local user name to be specified as an email address, and reinstate
928 * the original scursor on error return.
930 bool parseAddress( const char* & scursor
, const char * const send
,
931 Address
& result
, bool isCRLF
) {
932 // address := mailbox / group
934 eatCFWS( scursor
, send
, isCRLF
);
935 if ( scursor
== send
)
938 // first try if it's a single mailbox:
939 Mailbox maybeMailbox
;
940 const char * oldscursor
= scursor
;
941 if ( parseMailbox( scursor
, send
, maybeMailbox
, isCRLF
) ) {
943 result
.displayName
.clear();
944 result
.mailboxList
.append( maybeMailbox
);
947 scursor
= oldscursor
;
949 // KAlarm: Allow a local user name to be specified
950 // no, it's not a single mailbox. Try if it's a local user name:
951 QString maybeUserName
;
952 if ( parseUserName( scursor
, send
, maybeUserName
, isCRLF
) ) {
954 maybeMailbox
.setName( QString() );
956 addrSpec
.localPart
= maybeUserName
;
957 addrSpec
.domain
.clear();
958 maybeMailbox
.setAddress( addrSpec
);
959 result
.displayName
.clear();
960 result
.mailboxList
.append( maybeMailbox
);
963 scursor
= oldscursor
;
965 Address maybeAddress
;
967 // no, it's not a single mailbox. Try if it's a group:
968 if ( !parseGroup( scursor
, send
, maybeAddress
, isCRLF
) )
970 scursor
= oldscursor
; // KAlarm: reinstate original scursor on error return
974 result
= maybeAddress
;
978 } // namespace HeaderParsing