30 #include <kalarmcal/identities.h>
32 #include <kpimidentities/identitymanager.h>
33 #include <kpimidentities/identity.h>
34 #include <kpimutils/email.h>
35 #include <mailtransport/transportmanager.h>
36 #include <mailtransport/transport.h>
37 #include <mailtransport/messagequeuejob.h>
39 #include <kcalcore/person.h>
41 #include <kcal/person.h>
43 #include <kmime/kmime_header_parsing.h>
44 #include <kmime/kmime_headers.h>
45 #include <kmime/kmime_message.h>
47 #include <kstandarddirs.h>
49 #include <kaboutdata.h>
50 #include <kfileitem.h>
51 #include <kio/netaccess.h>
52 #include <ktemporaryfile.h>
53 #include <kemailsettings.h>
55 #include <kcharsets.h>
64 #include <QTextStream>
66 #include <QtDBus/QtDBus>
70 #ifdef KMAIL_SUPPORTED
71 #include "kmailinterface.h"
73 static const QLatin1String KMAIL_DBUS_SERVICE(
"org.kde.kmail");
77 namespace HeaderParsing
79 bool parseAddress(
const char* & scursor,
const char *
const send,
80 KMime::Types::Address & result,
bool isCRLF=
false );
84 static KMime::Types::Mailbox::List
parseAddresses(
const QString&
text, QString& invalidItem);
88 static const QTextCodec*
codecForName(
const QByteArray& str);
91 {
return i18nc(
"@info/plain",
"A 'From' email address must be configured in order to execute email alarms."); }
94 {
return i18nc(
"@info/plain KMail folder name: this should be translated the same as in kmail",
"sent-mail"); }
96 KAMail* KAMail::mInstance = 0;
97 QQueue<MailTransport::MessageQueueJob*> KAMail::mJobs;
98 QQueue<KAMail::JobData> KAMail::mJobData;
100 KAMail* KAMail::instance()
116 KPIMIdentities::Identity identity;
117 if (!jobdata.
event.emailFromId())
121 identity = Identities::identityManager()->identityForUoid(jobdata.
event.emailFromId());
122 if (identity.isNull())
124 kError() <<
"Identity" << jobdata.
event.emailFromId() <<
"not found";
125 errmsgs = errors(i18nc(
"@info",
"Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> not found", jobdata.
event.emailFromId()));
128 if (identity.primaryEmailAddress().isEmpty())
130 kError() <<
"Identity" << identity.identityName() <<
"uoid" << identity.uoid() <<
": no email address";
131 errmsgs = errors(i18nc(
"@info",
"Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> has no email address", identity.identityName()));
134 jobdata.
from = identity.fullEmailAddr();
136 if (jobdata.
from.isEmpty())
141 errmsgs = errors(i18nc(
"@info",
"<para>No 'From' email address is configured (no default email identity found)</para>"
142 "<para>Please set it in <application>KMail</application> or in the <application>KAlarm</application> Configuration dialog.</para>"));
145 errmsgs = errors(i18nc(
"@info",
"<para>No 'From' email address is configured.</para>"
146 "<para>Please set it in the KDE System Settings or in the <application>KAlarm</application> Configuration dialog.</para>"));
150 errmsgs = errors(i18nc(
"@info",
"<para>No 'From' email address is configured.</para>"
151 "<para>Please set it in the <application>KAlarm</application> Configuration dialog.</para>"));
157 kDebug() <<
"To:" << jobdata.
event.emailAddresses(QLatin1String(
","))
158 << endl <<
"Subject:" << jobdata.
event.emailSubject();
160 MailTransport::TransportManager* manager = MailTransport::TransportManager::self();
161 MailTransport::Transport* transport = 0;
164 kDebug() <<
"Sending via sendmail";
166 for (
int i = 0, count = transports.count(); i < count; ++i)
168 if (transports[i]->type() == MailTransport::Transport::EnumType::Sendmail)
171 transport = transports[i];
177 QString command = KStandardDirs::findExe(QLatin1String(
"sendmail"),
178 QLatin1String(
"/sbin:/usr/sbin:/usr/lib"));
179 transport = manager->createTransport();
180 transport->setName(QLatin1String(
"sendmail"));
181 transport->setType(MailTransport::Transport::EnumType::Sendmail);
182 transport->setHost(command);
183 transport->setRequiresAuthentication(
false);
184 transport->setStorePassword(
false);
185 manager->addTransport(transport);
186 transport->writeConfig();
187 kDebug() <<
"Creating sendmail transport, id=" << transport->id();
192 kDebug() <<
"Sending via KDE";
193 const int transportId = identity.transport().isEmpty() ? -1 : identity.transport().toInt();
194 transport = manager->transportById( transportId,
true );
197 kError() <<
"No mail transport found for identity" << identity.identityName() <<
"uoid" << identity.uoid();
198 errmsgs = errors(i18nc(
"@info",
"No mail transport configured for email identity <resource>%1</resource>", identity.identityName()));
202 kDebug() <<
"Using transport" << transport->name() <<
", id=" << transport->id();
204 KMime::Message::Ptr message = KMime::Message::Ptr(
new KMime::Message);
206 err = appendBodyAttachments(*message, jobdata);
209 kError() <<
"Error compiling message:" << err;
210 errmsgs = errors(err);
214 MailTransport::MessageQueueJob* mailjob =
new MailTransport::MessageQueueJob(kapp);
215 mailjob->setMessage(message);
216 mailjob->transportAttribute().setTransportId(transport->id());
221 if (!jobdata.
bcc.isEmpty())
223 MailTransport::SentBehaviourAttribute::SentBehaviour sentAction =
225 ? MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection : MailTransport::SentBehaviourAttribute::Delete;
226 mailjob->sentBehaviourAttribute().setSentBehaviour(sentAction);
227 mJobs.enqueue(mailjob);
228 mJobData.enqueue(jobdata);
229 if (mJobs.count() == 1)
232 connect(mailjob, SIGNAL(result(
KJob*)), instance(), SLOT(slotEmailSent(
KJob*)));
241 void KAMail::slotEmailSent(
KJob* job)
243 bool copyerr =
false;
247 kError() <<
"Failed:" << job->errorString();
248 errmsgs = errors(job->errorString(), SEND_ERROR);
251 if (mJobs.isEmpty() || mJobData.isEmpty() || job != mJobs.head())
254 kError() <<
"Wrong job at head of queue: wiping queue";
257 if (!errmsgs.isEmpty())
260 errmsgs += i18nc(
"@info",
"Emails may not have been sent");
261 errmsgs += i18nc(
"@info",
"Program error");
266 jobdata = mJobData.dequeue();
267 if (jobdata.allowNotify)
268 notifyQueued(jobdata.event);
270 if (!mJobs.isEmpty())
273 connect(mJobs.head(), SIGNAL(result(
KJob*)), instance(), SLOT(slotEmailSent(
KJob*)));
274 mJobs.head()->start();
283 KMime::Headers::Date*
date =
new KMime::Headers::Date;
285 message.setHeader(date);
287 KMime::Headers::From*
from =
new KMime::Headers::From;
289 message.setHeader(from);
291 KMime::Headers::To*
to =
new KMime::Headers::To;
293 KCalCore::Person::List toList = data.
event.emailAddressees();
297 for (
int i = 0, count = toList.count(); i < count; ++i)
299 to->addAddress(toList[i]->email().toLatin1(), toList[i]->name());
301 to->addAddress(toList[i].email().toLatin1(), toList[i].name());
303 message.setHeader(to);
305 if (!data.
bcc.isEmpty())
307 KMime::Headers::Bcc* bcc =
new KMime::Headers::Bcc;
309 message.setHeader(bcc);
312 KMime::Headers::Subject*
subject =
new KMime::Headers::Subject;
313 QString str = data.
event.emailSubject();
315 message.setHeader(subject);
317 KMime::Headers::UserAgent* agent =
new KMime::Headers::UserAgent;
318 agent->fromUnicodeString(KGlobal::mainComponent().aboutData()->programName() + QLatin1String(
"/" KALARM_VERSION),
"us-ascii");
319 message.setHeader(agent);
321 KMime::Headers::MessageID*
id =
new KMime::Headers::MessageID;
322 id->generate(data.
from.mid(data.
from.indexOf(QLatin1Char(
'@')) + 1).toLatin1());
323 message.setHeader(
id);
331 QString KAMail::appendBodyAttachments(KMime::Message& message, JobData& data)
333 QStringList attachments = data.event.emailAttachments();
334 if (!attachments.count())
337 message.contentType()->setMimeType(
"text/plain");
338 message.contentType()->setCharset(
"utf-8");
339 message.fromUnicodeString(data.event.message());
341 encodings.removeAll(KMime::Headers::CE8Bit);
342 message.contentTransferEncoding()->setEncoding(encodings[0]);
348 message.contentType()->setMimeType(
"multipart/mixed");
349 message.contentType()->setBoundary(KMime::multiPartBoundary());
351 if (!data.event.message().isEmpty())
354 KMime::Content* content =
new KMime::Content();
355 content->contentType()->setMimeType(
"text/plain");
356 content->contentType()->setCharset(
"utf-8");
357 content->fromUnicodeString(data.event.message());
359 encodings.removeAll(KMime::Headers::CE8Bit);
360 content->contentTransferEncoding()->setEncoding(encodings[0]);
362 message.addContent(content);
366 for (QStringList::Iterator at = attachments.begin(); at != attachments.end(); ++at)
368 QString attachment = QString::fromLatin1((*at).toLocal8Bit());
369 KUrl url(attachment);
370 QString attachError = i18nc(
"@info",
"Error attaching file: <filename>%1</filename>", attachment);
374 kError() <<
"Not found:" << attachment;
375 return i18nc(
"@info",
"Attachment not found: <filename>%1</filename>", attachment);
377 KFileItem fi(uds, url);
378 if (fi.isDir() || !fi.isReadable()) {
379 kError() <<
"Not file/not readable:" << attachment;
386 kError() <<
"Load failure:" << attachment;
390 if (!file.open(QIODevice::ReadOnly)) {
391 kDebug() <<
"tmp load error:" << attachment;
394 qint64 size = file.size();
395 QByteArray contents = file.readAll();
397 bool atterror =
false;
398 if (contents.size() < size) {
399 kDebug() <<
"Read error:" << attachment;
403 QByteArray coded = KCodecs::base64Encode(contents,
true);
404 KMime::Content* content =
new KMime::Content();
405 content->setBody(coded +
"\n\n");
408 KMimeType::Ptr type = KMimeType::findByUrl(url);
409 KMime::Headers::ContentType* ctype =
new KMime::Headers::ContentType(content);
411 ctype->setName(attachment,
"local");
412 content->setHeader(ctype);
415 KMime::Headers::ContentTransferEncoding* cte =
new KMime::Headers::ContentTransferEncoding(content);
416 cte->setEncoding(KMime::Headers::CEbase64);
417 cte->setDecoded(
false);
418 content->setHeader(cte);
420 message.addContent(content);
433 void KAMail::notifyQueued(
const KAEvent& event)
435 KMime::Types::Address addr;
436 QString localhost = QLatin1String(
"localhost");
437 QString hostname = QHostInfo::localHostName();
439 KCalCore::Person::List addresses =
event.emailAddressees();
443 for (
int i = 0, end = addresses.count(); i < end; ++i)
446 QByteArray email = addresses[i]->email().toLocal8Bit();
448 QByteArray email = addresses[i].email().toLocal8Bit();
450 const char* em = email;
454 QString domain = addr.mailboxList.first().addrSpec().domain;
455 if (!domain.isEmpty() && domain != localhost && domain != hostname)
470 return e.getSetting(KEMailSettings::EmailAddress);
486 const KMime::Types::Mailbox::List mailboxes =
parseAddresses(items, invalidItem);
487 if (!invalidItem.isEmpty())
489 for (
int i = 0, count = mailboxes.count(); i < count; ++i)
492 KCalCore::Person::Ptr person(
new KCalCore::Person(mailboxes[i].name(), mailboxes[i].addrSpec().asString()));
495 list += KCal::Person(mailboxes[i].name(), mailboxes[i].addrSpec().asString());
509 address = address.trimmed();
511 if (address.indexOf(QLatin1Char(
',')) >= 0 || address.indexOf(QLatin1Char(
';')) >= 0)
513 int n = address.length();
518 if (address[end] == QLatin1Char(
'>'))
521 if ((start = address.indexOf(QLatin1Char(
'<'))) < 0)
526 int i = address.indexOf(QLatin1Char(
'@'), start);
529 if (i == start || i == end)
560 int length = items.length();
561 for (
int next = 0; next < length; )
564 int i = items.indexOf(QLatin1Char(
','), next);
567 int sc = items.indexOf(QLatin1Char(
';'), next);
572 QString item = items.mid(next, i - next).trimmed();
575 case 1: list += item;
break;
578 default:
return item;
594 attachment = attachment.trimmed();
595 if (attachment.isEmpty())
617 KFileItem fi(uds, url);
618 if (fi.isDir() || !fi.isReadable())
626 QStringList KAMail::errors(
const QString& err, ErrType prefix)
631 case SEND_FAIL: error1 = i18nc(
"@info",
"Failed to send email");
break;
632 case SEND_ERROR: error1 = i18nc(
"@info",
"Error sending email");
break;
635 return QStringList(error1);
636 QStringList errs(QString::fromLatin1(
"%1:").arg(error1));
641 #ifdef KMAIL_SUPPORTED
648 args << serialNumber << (int)0;
650 #warning Set correct DBus interface/object for kmail
652 QDBusInterface iface(KMAIL_DBUS_SERVICE, QString(), QLatin1String(
"KMailIface"));
653 QDBusReply<QString> reply = iface.callWithArgumentList(QDBus::Block, QLatin1String(
"getDecodedBodyPart"), args);
654 if (!reply.isValid())
656 kError() <<
"D-Bus call failed:" << reply.error().message();
659 return reply.value();
668 return KPIMUtils::extractEmailAddress(KPIMUtils::normalizeAddressesAndEncodeIdn(emailAddress));
673 const QStringList splitEmails(KPIMUtils::splitAddressList(emailAddresses));
674 QStringList normalizedEmail;
675 Q_FOREACH(
const QString& email, splitEmails)
677 normalizedEmail << KPIMUtils::extractEmailAddress(KPIMUtils::normalizeAddressesAndEncodeIdn(email));
679 return normalizedEmail;
687 if (charsets.isEmpty())
688 charsets <<
"us-ascii" <<
"iso-8859-1" <<
"locale" <<
"utf-8";
690 for (
int i = 0, count = charsets.count(); i < count; ++i)
692 QByteArray encoding = charsets[i];
693 if (encoding ==
"locale")
696 kAsciiToLower(encoding.data());
700 if (encoding ==
"us-ascii")
702 if (KMime::isUsAscii(text))
709 kDebug() <<
"Auto-Charset: Something is wrong and I cannot get a codec. [" << encoding <<
"]";
712 if (codec->canEncode(text))
726 QByteArray codec = str;
727 kAsciiToLower(codec.data());
728 return KGlobal::charsets()->codecForName(QLatin1String(codec));
739 KMime::Types::Mailbox::List list;
747 for (
int i = 0, count = text.length(); i <= count; ++i)
753 char ch = text[i].toLatin1();
757 if (ch ==
' ' || ch ==
'\t')
760 state = (ch ==
'"') ? 10 : 1;
788 if (ch ==
',' || ch ==
';')
792 invalidItem = text.mid(start);
793 return KMime::Types::Mailbox::List();
797 if (ch ==
'"' && lastch !=
'\\')
823 QString addr = text.mid(startAddr, endAddr - startAddr);
824 KMime::Types::Mailbox mbox;
825 mbox.fromUnicodeString(addr);
826 if (mbox.address().isEmpty())
828 invalidItem = text.mid(start, endAddr - start);
829 return KMime::Types::Mailbox::List();
833 int len = endName - start;
834 QString name = text.mid(start, endName - start);
835 if (name[0] == QLatin1Char(
'"') && name[len - 1] == QLatin1Char(
'"'))
836 name = name.mid(1, len - 2);
841 endName = startAddr = endAddr = 0;
856 namespace HeaderParsing
859 using namespace KMime;
860 using namespace KMime::Types;
861 using namespace KMime::HeaderParsing;
868 QString & result,
bool isCRLF ) {
870 QString maybeLocalPart;
873 if ( scursor != send ) {
875 eatCFWS( scursor, send, isCRLF );
877 char ch = *scursor++;
887 if ( parseAtom( scursor, send, result,
false ) ) {
888 if (getpwnam(result.toLocal8Bit()))
903 Address & result,
bool isCRLF ) {
906 eatCFWS( scursor, send, isCRLF );
907 if ( scursor == send )
911 Mailbox maybeMailbox;
912 const char * oldscursor = scursor;
913 if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
915 result.displayName.clear();
916 result.mailboxList.append( maybeMailbox );
919 scursor = oldscursor;
923 QString maybeUserName;
924 if (
parseUserName( scursor, send, maybeUserName, isCRLF ) ) {
926 maybeMailbox.setName( QString() );
928 addrSpec.localPart = maybeUserName;
929 addrSpec.domain.clear();
930 maybeMailbox.setAddress( addrSpec );
931 result.displayName.clear();
932 result.mailboxList.append( maybeMailbox );
935 scursor = oldscursor;
937 Address maybeAddress;
940 if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) )
942 scursor = oldscursor;
946 result = maybeAddress;
static QString controlCentreAddress()
static MailClient emailClient()
Get Email client.
static void initHeaders(KMime::Message &, KAMail::JobData &)
static QStringList extractEmailsAndNormalize(const QString &emailAddresses)
void emailSent(KAMail::JobData &, const QStringList &errmsgs, bool copyerr=false)
virtual QByteArray text(quint32 serialNumber) const =0
static int checkAddress(QString &address)
static QString i18n_sent_mail()
the KAlarm application object
static QString convertAddresses(const QString &addresses, QList< KCal::Person > &)
static int checkAttachment(QString &attachment, KUrl *=0)
static KTimeZone timeZone(bool reload=false)
static QString i18n_NeedFromEmailAddress()
static QString convertAttachments(const QString &attachments, QStringList &list)
static const QLatin1String EMAIL_QUEUED_NOTIFY
static bool emailCopyToKMail()
static QByteArray autoDetectCharset(const QString &text)
static QString emailBccAddress()
static KMime::Types::Mailbox::List parseAddresses(const QString &text, QString &invalidItem)
static QString emailAddress()
static QString extractEmailAndNormalize(const QString &emailAddress)
static int send(JobData &, QStringList &errmsgs)
quint32 serialNumber() const
static MainWindow * mainMainWindow()
static const QTextCodec * codecForName(const QByteArray &str)
static MailFrom emailFrom()
static void information(QWidget *parent, const QString &text, const QString &caption=QString(), const QString &dontShowAgainName=QString(), Options options=Options(Notify|WindowModal))