kamail.cpp 33.7 KB
Newer Older
David Jarvie's avatar
David Jarvie committed
1
2
3
/*
 *  kamail.cpp  -  email functions
 *  Program:  kalarm
4
 *  Copyright © 2002-2011 by David Jarvie <djarvie@kde.org>
David Jarvie's avatar
David Jarvie committed
5
6
7
8
9
10
11
12
13
14
15
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
16
17
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
David Jarvie's avatar
David Jarvie committed
18
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
David Jarvie's avatar
David Jarvie committed
19
20
 */

David Jarvie's avatar
David Jarvie committed
21
22
23
24
25
26
#include "kalarm.h"   //krazy:exclude=includes (kalarm.h must be first)
#include "kamail.h"

#include "functions.h"
#include "kalarmapp.h"
#include "mainwindow.h"
27
#include "messagebox.h"
David Jarvie's avatar
David Jarvie committed
28
#include "preferences.h"
David Jarvie's avatar
David Jarvie committed
29

Laurent Montel's avatar
Laurent Montel committed
30
#include <kalarmcal/identities.h>
Laurent Montel's avatar
Laurent Montel committed
31
32
#include <KIdentityManagement/kidentitymanagement/identitymanager.h>
#include <KIdentityManagement/kidentitymanagement/identity.h>
Laurent Montel's avatar
Laurent Montel committed
33
34
35
#include <MailTransport/mailtransport/transportmanager.h>
#include <MailTransport/mailtransport/transport.h>
#include <MailTransport/mailtransport/messagequeuejob.h>
Christophe Giboudeaux's avatar
Christophe Giboudeaux committed
36
#include <KCalCore/Person>
Laurent Montel's avatar
Laurent Montel committed
37
38
39
#include <kmime/kmime_header_parsing.h>
#include <kmime/kmime_headers.h>
#include <kmime/kmime_message.h>
David Jarvie's avatar
David Jarvie committed
40

41
#include <KEmailAddress>
Laurent Montel's avatar
Laurent Montel committed
42
#include <K4AboutData>
43
#include <KLocale>
David Jarvie's avatar
David Jarvie committed
44
#include <KUrl>
Laurent Montel's avatar
Laurent Montel committed
45
#include <KLocalizedString>
David Jarvie's avatar
David Jarvie committed
46
47
#include <kfileitem.h>
#include <kio/netaccess.h>
48
#include <kemailsettings.h>
49
50
#include <kcodecs.h>
#include <kcharsets.h>
David Jarvie's avatar
David Jarvie committed
51

52
#include <QFile>
53
#include <QHostInfo>
54
55
56
#include <QList>
#include <QByteArray>
#include <QTextCodec>
David Jarvie's avatar
David Jarvie committed
57
#include <QStandardPaths>
58
#include <QtDBus/QtDBus>
Laurent Montel's avatar
Laurent Montel committed
59
#include "kalarm_debug.h"
David Jarvie's avatar
David Jarvie committed
60

61
#include <pwd.h>
David Jarvie's avatar
David Jarvie committed
62

63
#include "kmailinterface.h"
64

David Jarvie's avatar
David Jarvie committed
65
66
static const QLatin1String KMAIL_DBUS_SERVICE("org.kde.kmail");
//static const QLatin1String KMAIL_DBUS_PATH("/KMail");
David Jarvie's avatar
David Jarvie committed
67

68
69
namespace HeaderParsing
{
70
71
bool parseAddress( const char* & scursor, const char * const send,
                   KMime::Types::Address & result, bool isCRLF=false );
72
73
}

74
static void initHeaders(KMime::Message&, KAMail::JobData&);
75
static KMime::Types::Mailbox::List parseAddresses(const QString& text, QString& invalidItem);
76
77
static QString     extractEmailAndNormalize(const QString& emailAddress);
static QStringList extractEmailsAndNormalize(const QString& emailAddresses);
78
79
static QByteArray autoDetectCharset(const QString& text);
static const QTextCodec* codecForName(const QByteArray& str);
David Jarvie's avatar
David Jarvie committed
80

David Jarvie's avatar
David Jarvie committed
81
QString KAMail::i18n_NeedFromEmailAddress()
82
{ return i18nc("@info", "A 'From' email address must be configured in order to execute email alarms."); }
83

84
QString KAMail::i18n_sent_mail()
85
{ return i18nc("@info KMail folder name: this should be translated the same as in kmail", "sent-mail"); }
86

Laurent Montel's avatar
Laurent Montel committed
87
KAMail*                              KAMail::mInstance = Q_NULLPTR;   // used only to enable signals/slots to work
88
89
QQueue<MailTransport::MessageQueueJob*> KAMail::mJobs;
QQueue<KAMail::JobData>                 KAMail::mJobData;
90
91
92

KAMail* KAMail::instance()
{
93
94
95
    if (!mInstance)
        mInstance = new KAMail();
    return mInstance;
96
97
}

David Jarvie's avatar
David Jarvie committed
98
99
/******************************************************************************
* Send the email message specified in an event.
100
101
102
* Reply = 1 if the message was sent - 'errmsgs' may contain copy error messages.
*       = 0 if the message is queued for sending.
*       = -1 if the message was not sent - 'errmsgs' contains the error messages.
David Jarvie's avatar
David Jarvie committed
103
*/
104
int KAMail::send(JobData& jobdata, QStringList& errmsgs)
David Jarvie's avatar
David Jarvie committed
105
{
106
    QString err;
Laurent Montel's avatar
Laurent Montel committed
107
    KIdentityManagement::Identity identity;
108
109
110
111
    if (!jobdata.event.emailFromId())
        jobdata.from = Preferences::emailAddress();
    else
    {
112
        identity = Identities::identityManager()->identityForUoid(jobdata.event.emailFromId());
113
114
        if (identity.isNull())
        {
Laurent Montel's avatar
Laurent Montel committed
115
            qCCritical(KALARM_LOG) << "Identity" << jobdata.event.emailFromId() << "not found";
Laurent Montel's avatar
Laurent Montel committed
116
            errmsgs = errors(xi18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> not found", jobdata.event.emailFromId()));
117
118
119
120
            return -1;
        }
        if (identity.primaryEmailAddress().isEmpty())
        {
Laurent Montel's avatar
Laurent Montel committed
121
            qCCritical(KALARM_LOG) << "Identity" << identity.identityName() << "uoid" << identity.uoid() << ": no email address";
Laurent Montel's avatar
Laurent Montel committed
122
            errmsgs = errors(xi18nc("@info", "Invalid 'From' email address.<nl/>Email identity <resource>%1</resource> has no email address", identity.identityName()));
123
124
125
126
127
128
129
130
131
            return -1;
        }
        jobdata.from = identity.fullEmailAddr();
    }
    if (jobdata.from.isEmpty())
    {
        switch (Preferences::emailFrom())
        {
            case Preferences::MAIL_FROM_KMAIL:
Laurent Montel's avatar
Laurent Montel committed
132
                errmsgs = errors(xi18nc("@info", "<para>No 'From' email address is configured (no default email identity found)</para>"
133
134
135
                                                "<para>Please set it in <application>KMail</application> or in the <application>KAlarm</application> Configuration dialog.</para>"));
                break;
            case Preferences::MAIL_FROM_SYS_SETTINGS:
Laurent Montel's avatar
Laurent Montel committed
136
                errmsgs = errors(xi18nc("@info", "<para>No 'From' email address is configured.</para>"
137
138
139
140
                                                "<para>Please set it in the KDE System Settings or in the <application>KAlarm</application> Configuration dialog.</para>"));
                break;
            case Preferences::MAIL_FROM_ADDR:
            default:
Laurent Montel's avatar
Laurent Montel committed
141
                errmsgs = errors(xi18nc("@info", "<para>No 'From' email address is configured.</para>"
142
143
144
145
146
147
                                                "<para>Please set it in the <application>KAlarm</application> Configuration dialog.</para>"));
                break;
        }
        return -1;
    }
    jobdata.bcc  = (jobdata.event.emailBcc() ? Preferences::emailBccAddress() : QString());
David Jarvie's avatar
David Jarvie committed
148
    qCDebug(KALARM_LOG) << "To:" << jobdata.event.emailAddresses(QStringLiteral(","))
149
150
151
                  << endl << "Subject:" << jobdata.event.emailSubject();

    MailTransport::TransportManager* manager = MailTransport::TransportManager::self();
Laurent Montel's avatar
Laurent Montel committed
152
    MailTransport::Transport* transport = Q_NULLPTR;
153
154
#warning Remove sendmail support!
#if 0
155
156
    if (Preferences::emailClient() == Preferences::sendmail)
    {
Laurent Montel's avatar
Laurent Montel committed
157
        qCDebug(KALARM_LOG) << "Sending via sendmail";
158
159
160
161
162
163
164
165
166
167
168
169
        const QList<MailTransport::Transport*> transports = manager->transports();
        for (int i = 0, count = transports.count();  i < count;  ++i)
        {
            if (transports[i]->type() == MailTransport::Transport::EnumType::Sendmail)
            {
                // Use the first sendmail transport found
                transport = transports[i];
                break;
            }
        }
        if (!transport)
        {
170
            QStringList paths;
Laurent Montel's avatar
Laurent Montel committed
171
            paths << QStringLiteral("/sbin") << QStringLiteral("/usr/sbin") << QStringLiteral("/usr/lib");
Laurent Montel's avatar
Laurent Montel committed
172
            QString command = QStandardPaths::findExecutable(QStringLiteral("sendmail"), paths);
173
            transport = manager->createTransport();
David Jarvie's avatar
David Jarvie committed
174
            transport->setName(QStringLiteral("sendmail"));
175
176
177
178
179
            transport->setType(MailTransport::Transport::EnumType::Sendmail);
            transport->setHost(command);
            transport->setRequiresAuthentication(false);
            transport->setStorePassword(false);
            manager->addTransport(transport);
Laurent Montel's avatar
Laurent Montel committed
180
            transport->save();
Laurent Montel's avatar
Laurent Montel committed
181
            qCDebug(KALARM_LOG) << "Creating sendmail transport, id=" << transport->id();
182
183
184
        }
    }
    else
185
#endif
186
    {
Laurent Montel's avatar
Laurent Montel committed
187
        qCDebug(KALARM_LOG) << "Sending via KDE";
188
        const int transportId = identity.transport().isEmpty() ? -1 : identity.transport().toInt();
David Jarvie's avatar
David Jarvie committed
189
        transport = manager->transportById(transportId, true);
190
191
        if (!transport)
        {
Laurent Montel's avatar
Laurent Montel committed
192
            qCCritical(KALARM_LOG) << "No mail transport found for identity" << identity.identityName() << "uoid" << identity.uoid();
Laurent Montel's avatar
Laurent Montel committed
193
            errmsgs = errors(xi18nc("@info", "No mail transport configured for email identity <resource>%1</resource>", identity.identityName()));
194
195
196
            return -1;
        }
    }
Laurent Montel's avatar
Laurent Montel committed
197
    qCDebug(KALARM_LOG) << "Using transport" << transport->name() << ", id=" << transport->id();
198
199
200
201
202
203

    KMime::Message::Ptr message = KMime::Message::Ptr(new KMime::Message);
    initHeaders(*message, jobdata);
    err = appendBodyAttachments(*message, jobdata);
    if (!err.isNull())
    {
Laurent Montel's avatar
Laurent Montel committed
204
        qCCritical(KALARM_LOG) << "Error compiling message:" << err;
205
206
207
        errmsgs = errors(err);
        return -1;
    }
208

David Jarvie's avatar
David Jarvie committed
209
210
211
    MailTransport::MessageQueueJob* mailjob = new MailTransport::MessageQueueJob(kapp);
    mailjob->setMessage(message);
    mailjob->transportAttribute().setTransportId(transport->id());
212
213
214
    // MessageQueueJob email addresses must be pure, i.e. without display name. Note
    // that display names are included in the actual headers set up by initHeaders().
    mailjob->addressAttribute().setFrom(extractEmailAndNormalize(jobdata.from));
David Jarvie's avatar
David Jarvie committed
215
    mailjob->addressAttribute().setTo(extractEmailsAndNormalize(jobdata.event.emailAddresses(QStringLiteral(","))));
216
    if (!jobdata.bcc.isEmpty())
217
        mailjob->addressAttribute().setBcc(extractEmailsAndNormalize(jobdata.bcc));
David Jarvie's avatar
David Jarvie committed
218
    MailTransport::SentBehaviourAttribute::SentBehaviour sentAction =
219
220
                         (Preferences::emailClient() == Preferences::kmail || Preferences::emailCopyToKMail())
                         ? MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection : MailTransport::SentBehaviourAttribute::Delete;
David Jarvie's avatar
David Jarvie committed
221
    mailjob->sentBehaviourAttribute().setSentBehaviour(sentAction);
222
223
224
225
226
227
228
229
230
    mJobs.enqueue(mailjob);
    mJobData.enqueue(jobdata);
    if (mJobs.count() == 1)
    {
        // There are no jobs already active or queued, so send now
        connect(mailjob, SIGNAL(result(KJob*)), instance(), SLOT(slotEmailSent(KJob*)));
        mailjob->start();
    }
    return 0;
David Jarvie's avatar
David Jarvie committed
231
232
233
}

/******************************************************************************
234
* Called when sending an email is complete.
David Jarvie's avatar
David Jarvie committed
235
*/
236
void KAMail::slotEmailSent(KJob* job)
David Jarvie's avatar
David Jarvie committed
237
{
238
239
240
241
    bool copyerr = false;
    QStringList errmsgs;
    if (job->error())
    {
Laurent Montel's avatar
Laurent Montel committed
242
        qCCritical(KALARM_LOG) << "Failed:" << job->errorString();
243
244
245
246
247
248
        errmsgs = errors(job->errorString(), SEND_ERROR);
    }
    JobData jobdata;
    if (mJobs.isEmpty()  ||  mJobData.isEmpty()  ||  job != mJobs.head())
    {
        // The queue has been corrupted, so we can't locate the job's data
Laurent Montel's avatar
Laurent Montel committed
249
        qCCritical(KALARM_LOG) << "Wrong job at head of queue: wiping queue";
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
        mJobs.clear();
        mJobData.clear();
        if (!errmsgs.isEmpty())
            theApp()->emailSent(jobdata, errmsgs);
        errmsgs.clear();
        errmsgs += i18nc("@info", "Emails may not have been sent");
        errmsgs += i18nc("@info", "Program error");
        theApp()->emailSent(jobdata, errmsgs);
        return;
    }
    mJobs.dequeue();
    jobdata = mJobData.dequeue();
    if (jobdata.allowNotify)
        notifyQueued(jobdata.event);
    theApp()->emailSent(jobdata, errmsgs, copyerr);
    if (!mJobs.isEmpty())
    {
        // Send the next queued email
        connect(mJobs.head(), SIGNAL(result(KJob*)), instance(), SLOT(slotEmailSent(KJob*)));
        mJobs.head()->start();
    }
David Jarvie's avatar
David Jarvie committed
271
272
}

David Jarvie's avatar
David Jarvie committed
273
274
275
/******************************************************************************
* Create the headers part of the email.
*/
276
void initHeaders(KMime::Message& message, KAMail::JobData& data)
David Jarvie's avatar
David Jarvie committed
277
{
278
    KMime::Headers::Date* date = new KMime::Headers::Date;
Laurent Montel's avatar
Laurent Montel committed
279
280
//QT5 port it
    date->setDateTime(QDateTime::currentDateTime(/*Preferences::timeZone()*/));
281
    message.setHeader(date);
282

283
284
285
    KMime::Headers::From* from = new KMime::Headers::From;
    from->fromUnicodeString(data.from, autoDetectCharset(data.from));
    message.setHeader(from);
286

287
    KMime::Headers::To* to = new KMime::Headers::To;
288
    KCalCore::Person::List toList = data.event.emailAddressees();
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    for (int i = 0, count = toList.count();  i < count;  ++i)
        to->addAddress(toList[i]->email().toLatin1(), toList[i]->name());
    message.setHeader(to);

    if (!data.bcc.isEmpty())
    {
        KMime::Headers::Bcc* bcc = new KMime::Headers::Bcc;
        bcc->fromUnicodeString(data.bcc, autoDetectCharset(data.bcc));
        message.setHeader(bcc);
    }

    KMime::Headers::Subject* subject = new KMime::Headers::Subject;
    QString str = data.event.emailSubject();
    subject->fromUnicodeString(str, autoDetectCharset(str));
    message.setHeader(subject);

    KMime::Headers::UserAgent* agent = new KMime::Headers::UserAgent;
Laurent Montel's avatar
Laurent Montel committed
306
    agent->fromUnicodeString(KComponentData::mainComponent().aboutData()->programName() + QLatin1String("/" KALARM_VERSION), "us-ascii");
307
308
309
    message.setHeader(agent);

    KMime::Headers::MessageID* id = new KMime::Headers::MessageID;
Laurent Montel's avatar
Laurent Montel committed
310
    id->generate(data.from.mid(data.from.indexOf(QLatin1Char('@')) + 1).toLatin1());
311
    message.setHeader(id);
David Jarvie's avatar
David Jarvie committed
312
313
}

David Jarvie's avatar
David Jarvie committed
314
315
/******************************************************************************
* Append the body and attachments to the email text.
316
* Reply = reason for error
317
*       = empty string if successful.
David Jarvie's avatar
David Jarvie committed
318
*/
319
QString KAMail::appendBodyAttachments(KMime::Message& message, JobData& data)
David Jarvie's avatar
David Jarvie committed
320
{
321
322
323
324
325
326
327
    QStringList attachments = data.event.emailAttachments();
    if (!attachments.count())
    {
        // There are no attachments, so simply append the message body
        message.contentType()->setMimeType("text/plain");
        message.contentType()->setCharset("utf-8");
        message.fromUnicodeString(data.event.message());
328
        auto encodings = KMime::encodingsForData(message.body());
329
330
        encodings.removeAll(KMime::Headers::CE8Bit);  // not handled by KMime
        message.contentTransferEncoding()->setEncoding(encodings[0]);
331
332
333
334
335
        message.assemble();
    }
    else
    {
        // There are attachments, so the message must be in MIME format
David Jarvie's avatar
David Jarvie committed
336
        message.contentType()->setMimeType("multipart/mixed");
337
338
339
340
341
342
343
344
345
        message.contentType()->setBoundary(KMime::multiPartBoundary());

        if (!data.event.message().isEmpty())
        {
            // There is a message body
            KMime::Content* content = new KMime::Content();
            content->contentType()->setMimeType("text/plain");
            content->contentType()->setCharset("utf-8");
            content->fromUnicodeString(data.event.message());
346
            auto encodings = KMime::encodingsForData(content->body());
347
348
            encodings.removeAll(KMime::Headers::CE8Bit);  // not handled by KMime
            content->contentTransferEncoding()->setEncoding(encodings[0]);
349
350
351
352
353
354
355
            content->assemble();
            message.addContent(content);
        }

        // Append each attachment in turn
        for (QStringList::Iterator at = attachments.begin();  at != attachments.end();  ++at)
        {
Laurent Montel's avatar
Laurent Montel committed
356
            QString attachment = QString::fromLatin1((*at).toLocal8Bit());
357
            KUrl url(attachment);
Laurent Montel's avatar
Laurent Montel committed
358
            QString attachError = xi18nc("@info", "Error attaching file: <filename>%1</filename>", attachment);
359
360
            url.cleanPath();
            KIO::UDSEntry uds;
David Jarvie's avatar
David Jarvie committed
361
362
            if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow()))
            {
Laurent Montel's avatar
Laurent Montel committed
363
                qCCritical(KALARM_LOG) << "Not found:" << attachment;
Laurent Montel's avatar
Laurent Montel committed
364
                return xi18nc("@info", "Attachment not found: <filename>%1</filename>", attachment);
365
366
            }
            KFileItem fi(uds, url);
David Jarvie's avatar
David Jarvie committed
367
368
            if (fi.isDir()  ||  !fi.isReadable())
            {
Laurent Montel's avatar
Laurent Montel committed
369
                qCCritical(KALARM_LOG) << "Not file/not readable:" << attachment;
370
371
372
373
374
                return attachError;
            }

            // Read the file contents
            QString tmpFile;
David Jarvie's avatar
David Jarvie committed
375
376
            if (!KIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow()))
            {
Laurent Montel's avatar
Laurent Montel committed
377
                qCCritical(KALARM_LOG) << "Load failure:" << attachment;
378
379
380
                return attachError;
            }
            QFile file(tmpFile);
David Jarvie's avatar
David Jarvie committed
381
382
            if (!file.open(QIODevice::ReadOnly))
            {
Laurent Montel's avatar
Laurent Montel committed
383
                qCDebug(KALARM_LOG) << "tmp load error:" << attachment;
384
385
386
387
388
389
                return attachError;
            }
            qint64 size = file.size();
            QByteArray contents = file.readAll();
            file.close();
            bool atterror = false;
David Jarvie's avatar
David Jarvie committed
390
391
            if (contents.size() < size)
            {
Laurent Montel's avatar
Laurent Montel committed
392
                qCDebug(KALARM_LOG) << "Read error:" << attachment;
393
394
395
396
397
398
399
400
                atterror = true;
            }

            QByteArray coded = KCodecs::base64Encode(contents, true);
            KMime::Content* content = new KMime::Content();
            content->setBody(coded + "\n\n");

            // Set the content type
Laurent Montel's avatar
Laurent Montel committed
401
402
            QMimeDatabase mimeDb;
            QString typeName = mimeDb.mimeTypeForUrl(url).name();
403
            KMime::Headers::ContentType* ctype = new KMime::Headers::ContentType;
Laurent Montel's avatar
Laurent Montel committed
404
            ctype->fromUnicodeString(typeName, autoDetectCharset(typeName));
405
406
407
408
            ctype->setName(attachment, "local");
            content->setHeader(ctype);

            // Set the encoding
409
            KMime::Headers::ContentTransferEncoding* cte = new KMime::Headers::ContentTransferEncoding;
410
411
412
413
414
415
416
417
418
419
420
            cte->setEncoding(KMime::Headers::CEbase64);
            cte->setDecoded(false);
            content->setHeader(cte);
            content->assemble();
            message.addContent(content);
            if (atterror)
                return attachError;
        }
        message.assemble();
    }
    return QString();
David Jarvie's avatar
David Jarvie committed
421
422
}

423
424
425
426
/******************************************************************************
* If any of the destination email addresses are non-local, display a
* notification message saying that an email has been queued for sending.
*/
427
void KAMail::notifyQueued(const KAEvent& event)
428
{
429
    KMime::Types::Address addr;
Laurent Montel's avatar
Laurent Montel committed
430
    const QString localhost = QStringLiteral("localhost");
David Jarvie's avatar
David Jarvie committed
431
    const QString hostname  = QHostInfo::localHostName();
432
    KCalCore::Person::List addresses = event.emailAddressees();
433
434
435
436
437
438
439
    for (int i = 0, end = addresses.count();  i < end;  ++i)
    {
        QByteArray email = addresses[i]->email().toLocal8Bit();
        const char* em = email;
        if (!email.isEmpty()
        &&  HeaderParsing::parseAddress(em, em + email.length(), addr))
        {
Laurent Montel's avatar
Laurent Montel committed
440
            QString domain = addr.mailboxList.at(0).addrSpec().domain;
441
442
            if (!domain.isEmpty()  &&  domain != localhost  &&  domain != hostname)
            {
David Jarvie's avatar
David Jarvie committed
443
                KAMessageBox::information(MainWindow::mainMainWindow(), i18nc("@info", "An email has been queued to be sent"), QString(), Preferences::EMAIL_QUEUED_NOTIFY);
444
445
446
447
                return;
            }
        }
    }
448
449
}

450
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
451
* Fetch the user's email address configured in the KDE System Settings.
452
453
454
*/
QString KAMail::controlCentreAddress()
{
455
456
    KEMailSettings e;
    return e.getSetting(KEMailSettings::EmailAddress);
457
458
}

David Jarvie's avatar
David Jarvie committed
459
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
460
461
462
* Parse a list of email addresses, optionally containing display names,
* entered by the user.
* Reply = the invalid item if error, else empty string.
David Jarvie's avatar
David Jarvie committed
463
*/
464
QString KAMail::convertAddresses(const QString& items, KCalCore::Person::List& list)
David Jarvie's avatar
David Jarvie committed
465
{
466
467
468
469
470
471
472
473
474
475
476
    list.clear();
    QString invalidItem;
    const KMime::Types::Mailbox::List mailboxes = parseAddresses(items, invalidItem);
    if (!invalidItem.isEmpty())
        return invalidItem;
    for (int i = 0, count = mailboxes.count();  i < count;  ++i)
    {
        KCalCore::Person::Ptr person(new KCalCore::Person(mailboxes[i].name(), mailboxes[i].addrSpec().asString()));
        list += person;
    }
    return QString();
David Jarvie's avatar
David Jarvie committed
477
478
479
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
480
481
482
483
* Check the validity of an email address.
* Because internal email addresses don't have to abide by the usual internet
* email address rules, only some basic checks are made.
* Reply = 1 if alright, 0 if empty, -1 if error.
David Jarvie's avatar
David Jarvie committed
484
485
486
*/
int KAMail::checkAddress(QString& address)
{
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
    address = address.trimmed();
    // Check that there are no list separator characters present
    if (address.indexOf(QLatin1Char(',')) >= 0  ||  address.indexOf(QLatin1Char(';')) >= 0)
        return -1;
    int n = address.length();
    if (!n)
        return 0;
    int start = 0;
    int end   = n - 1;
    if (address[end] == QLatin1Char('>'))
    {
        // The email address is in <...>
        if ((start = address.indexOf(QLatin1Char('<'))) < 0)
            return -1;
        ++start;
        --end;
    }
    int i = address.indexOf(QLatin1Char('@'), start);
    if (i >= 0)
    {
        if (i == start  ||  i == end)          // check @ isn't the first or last character
//        ||  address.indexOf(QLatin1Char('@'), i + 1) >= 0)    // check for multiple @ characters
            return -1;
    }
/*    else
    {
        // Allow the @ character to be missing if it's a local user
        if (!getpwnam(address.mid(start, end - start + 1).toLocal8Bit()))
            return false;
    }
    for (int i = start;  i <= end;  ++i)
    {
        char ch = address[i].toLatin1();
        if (ch == '.'  ||  ch == '@'  ||  ch == '-'  ||  ch == '_'
        ||  (ch >= 'A' && ch <= 'Z')  ||  (ch >= 'a' && ch <= 'z')
        ||  (ch >= '0' && ch <= '9'))
            continue;
        return false;
    }*/
    return 1;
David Jarvie's avatar
David Jarvie committed
527
528
529
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
530
531
532
* Convert a comma or semicolon delimited list of attachments into a
* QStringList. The items are checked for validity.
* Reply = the invalid item if error, else empty string.
533
534
535
*/
QString KAMail::convertAttachments(const QString& items, QStringList& list)
{
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
    KUrl url;
    list.clear();
    int length = items.length();
    for (int next = 0;  next < length;  )
    {
        // Find the first delimiter character (, or ;)
        int i = items.indexOf(QLatin1Char(','), next);
        if (i < 0)
            i = items.length();
        int sc = items.indexOf(QLatin1Char(';'), next);
        if (sc < 0)
            sc = items.length();
        if (sc < i)
            i = sc;
        QString item = items.mid(next, i - next).trimmed();
        switch (checkAttachment(item))
        {
            case 1:   list += item;  break;
            case 0:   break;          // empty attachment name
            case -1:
            default:  return item;    // error
        }
        next = i + 1;
    }
    return QString();
561
562
563
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
564
565
566
567
568
* Check for the existence of the attachment file.
* If non-null, '*url' receives the KUrl of the attachment.
* Reply = 1 if attachment exists
*       = 0 if null name
*       = -1 if doesn't exist.
David Jarvie's avatar
David Jarvie committed
569
*/
Laurent Montel's avatar
Laurent Montel committed
570
int KAMail::checkAttachment(QString& attachment, KUrl* url)
David Jarvie's avatar
David Jarvie committed
571
{
572
573
574
575
576
577
578
579
580
581
582
583
584
    attachment = attachment.trimmed();
    if (attachment.isEmpty())
    {
        if (url)
            *url = KUrl();
        return 0;
    }
    // Check that the file exists
    KUrl u(attachment);
    u.cleanPath();
    if (url)
        *url = u;
    return checkAttachment(u) ? 1 : -1;
585
586
587
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
588
* Check for the existence of the attachment file.
589
*/
Laurent Montel's avatar
Laurent Montel committed
590
bool KAMail::checkAttachment(const KUrl& url)
591
{
592
593
594
595
596
597
598
    KIO::UDSEntry uds;
    if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow()))
        return false;       // doesn't exist
    KFileItem fi(uds, url);
    if (fi.isDir()  ||  !fi.isReadable())
        return false;
    return true;
David Jarvie's avatar
David Jarvie committed
599
600
}

601
602
603
/******************************************************************************
* Set the appropriate error messages for a given error string.
*/
604
QStringList KAMail::errors(const QString& err, ErrType prefix)
605
{
606
607
608
609
610
611
612
613
614
615
616
    QString error1;
    switch (prefix)
    {
        case SEND_FAIL:  error1 = i18nc("@info", "Failed to send email");  break;
        case SEND_ERROR:  error1 = i18nc("@info", "Error sending email");  break;
    }
    if (err.isEmpty())
        return QStringList(error1);
    QStringList errs(QString::fromLatin1("%1:").arg(error1));
    errs += err;
    return errs;
617
618
}

619
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
620
* Get the body of an email from KMail, given its serial number.
621
*/
Ingo Klöcker's avatar
Ingo Klöcker committed
622
QString KAMail::getMailBody(quint32 serialNumber)
623
{
624
625
    QList<QVariant> args;
    args << serialNumber << (int)0;
626
#ifdef __GNUC__
David Jarvie's avatar
David Jarvie committed
627
#warning Set correct DBus interface/object for kmail
628
#endif
Laurent Montel's avatar
Laurent Montel committed
629
630
    QDBusInterface iface(KMAIL_DBUS_SERVICE, QString(), QStringLiteral("KMailIface"));
    QDBusReply<QString> reply = iface.callWithArgumentList(QDBus::Block, QStringLiteral("getDecodedBodyPart"), args);
631
632
    if (!reply.isValid())
    {
Laurent Montel's avatar
Laurent Montel committed
633
        qCCritical(KALARM_LOG) << "D-Bus call failed:" << reply.error().message();
634
635
636
        return QString();
    }
    return reply.value();
637
638
}

639
640
641
642
643
/******************************************************************************
* Extract the pure addresses from given email addresses.
*/
QString extractEmailAndNormalize(const QString& emailAddress)
{
644
    return KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(emailAddress));
645
646
647
648
}

QStringList extractEmailsAndNormalize(const QString& emailAddresses)
{
649
    const QStringList splitEmails(KEmailAddress::splitAddressList(emailAddresses));
650
651
652
    QStringList normalizedEmail;
    Q_FOREACH(const QString& email, splitEmails)
    {
653
        normalizedEmail << KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(email));
654
655
656
657
    }
    return normalizedEmail;
}

658
//-----------------------------------------------------------------------------
659
// Based on KMail KMMsgBase::autoDetectCharset().
660
661
662
663
664
665
666
667
668
669
670
QByteArray autoDetectCharset(const QString& text)
{
    static QList<QByteArray> charsets;
    if (charsets.isEmpty())
        charsets << "us-ascii" << "iso-8859-1" << "locale" << "utf-8";

    for (int i = 0, count = charsets.count();  i < count;  ++i)
    {
        QByteArray encoding = charsets[i];
        if (encoding == "locale")
        {
671
            encoding = QTextCodec::codecForName(KLocale::global()->encoding())->name();
672
            encoding = encoding.toLower();
673
674
675
676
677
678
679
680
681
682
683
684
        }
        if (text.isEmpty())
            return encoding;
        if (encoding == "us-ascii")
        {
            if (KMime::isUsAscii(text))
                return encoding;
        }
        else
        {
            const QTextCodec *codec = codecForName(encoding);
            if (!codec)
Laurent Montel's avatar
Laurent Montel committed
685
                qCDebug(KALARM_LOG) <<"Auto-Charset: Something is wrong and I cannot get a codec. [" << encoding <<"]";
686
687
688
689
690
691
692
            else
            {
                 if (codec->canEncode(text))
                     return encoding;
            }
        }
    }
693
    return QByteArray();
694
695
696
}

//-----------------------------------------------------------------------------
697
// Based on KMail KMMsgBase::codecForName().
698
699
700
const QTextCodec* codecForName(const QByteArray& str)
{
    if (str.isEmpty())
Laurent Montel's avatar
Laurent Montel committed
701
        return Q_NULLPTR;
702
    QByteArray codec = str.toLower();
Laurent Montel's avatar
Laurent Montel committed
703
    return KCharsets::charsets()->codecForName(QLatin1String(codec));
704
705
706
}

/******************************************************************************
707
708
709
710
* Parse a string containing multiple addresses, separated by comma or semicolon,
* while retaining Unicode name parts.
* Note that this only needs to parse strings input into KAlarm, so it only
* needs to accept the common syntax for email addresses, not obsolete syntax.
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
*/
KMime::Types::Mailbox::List parseAddresses(const QString& text, QString& invalidItem)
{
    KMime::Types::Mailbox::List list;
    int state     = 0;
    int start     = 0;  // start of this item
    int endName   = 0;  // character after end of name
    int startAddr = 0;  // start of address
    int endAddr   = 0;  // character after end of address
    char lastch = '\0';
    bool ended    = false;   // found the end of the item
    for (int i = 0, count = text.length();  i <= count;  ++i)
    {
        if (i == count)
            ended = true;
        else
        {
            char ch = text[i].toLatin1();
            switch (state)
            {
                case 0:   // looking for start of item
                    if (ch == ' ' || ch == '\t')
                        continue;
                    start = i;
                    state = (ch == '"') ? 10 : 1;
                    break;
                case 1:   // looking for start of address, or end of item
                    switch (ch)
                    {
                        case '<':
                            startAddr = i + 1;
                            state = 2;
                            break;
                        case ',':
                        case ';':
                            ended = true;
                            break;
                        case ' ':
                            break;
                        default:
                            endName = i + 1;
                            break;
                    }
                    break;
                case 2:   // looking for '>' at end of address
                    if (ch == '>')
                    {
                        endAddr = i;
                        state = 3;
                    }
                    break;
                case 3:   // looking for item separator
                    if (ch == ','  ||  ch == ';')
                        ended = true;
                    else if (ch != ' ')
                    {
                        invalidItem = text.mid(start);
                        return KMime::Types::Mailbox::List();
                    }
                    break;
                case 10:   // looking for closing quote
                    if (ch == '"'  &&  lastch != '\\')
                    {
                        ++start;   // remove opening quote from name
                        endName = i;
                        state = 11;
                    }
                    lastch = ch;
                    break;
                case 11:   // looking for '<'
                    if (ch == '<')
                    {
                        startAddr = i + 1;
                        state = 2;
                    }
                    break;
            }
        }
        if (ended)
        {
            // Found the end of the item - add it to the list
            if (!startAddr)
            {
                startAddr = start;
                endAddr   = endName;
                endName   = 0;
            }
            QString addr = text.mid(startAddr, endAddr - startAddr);
            KMime::Types::Mailbox mbox;
            mbox.fromUnicodeString(addr);
            if (mbox.address().isEmpty())
            {
                invalidItem = text.mid(start, endAddr - start);
                return KMime::Types::Mailbox::List();
            }
            if (endName)
            {
                int len = endName - start;
                QString name = text.mid(start, endName - start);
Laurent Montel's avatar
Laurent Montel committed
810
                if (name[0] == QLatin1Char('"')  &&  name[len - 1] == QLatin1Char('"'))
811
812
813
814
815
816
817
818
819
820
821
822
823
824
                    name = name.mid(1, len - 2);
                mbox.setName(name);
            }
            list.append(mbox);

            endName = startAddr = endAddr = 0;
            start = i + 1;
            state = 0;
            ended = false;
        }
    }
    return list;
}

825
/*=============================================================================
826
=  HeaderParsing :  modified and additional functions.
827
=  The following functions are modified from, or additional to, those in
828
=  libkmime kmime_header_parsing.cpp.
829
830
831
832
833
834
835
836
837
838
=============================================================================*/

namespace HeaderParsing
{

using namespace KMime;
using namespace KMime::Types;
using namespace KMime::HeaderParsing;

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
839
840
* New function.
* Allow a local user name to be specified as an email address.
841
842
843
844
845
846
847
*/
bool parseUserName( const char* & scursor, const char * const send,
                    QString & result, bool isCRLF ) {

  QString maybeLocalPart;
  QString tmp;

David Jarvie's avatar
David Jarvie committed
848
  if ( scursor != send ) {
849
850
851
852
853
854
855
856
857
858
859
860
    // first, eat any whitespace
    eatCFWS( scursor, send, isCRLF );

    char ch = *scursor++;
    switch ( ch ) {
    case '.': // dot
    case '@':
    case '"': // quoted-string
      return false;

    default: // atom
      scursor--; // re-set scursor to point to ch again
Laurent Montel's avatar
Laurent Montel committed
861
      tmp.clear();
862
      if ( parseAtom( scursor, send, result, false /* no 8bit */ ) ) {
David Jarvie's avatar
David Jarvie committed
863
        if (getpwnam(result.toLocal8Bit()))
864
865
866
867
868
869
870
871
872
          return true;
      }
      return false; // parseAtom can only fail if the first char is non-atext.
    }
  }
  return false;
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
873
874
875
* Modified function.
* Allow a local user name to be specified as an email address, and reinstate
* the original scursor on error return.
876
877
*/
bool parseAddress( const char* & scursor, const char * const send,
878
                   Address & result, bool isCRLF ) {
879
880
881
  // address       := mailbox / group

  eatCFWS( scursor, send, isCRLF );
882
883
  if ( scursor == send )
    return false;
884
885
886
887
888
889

  // first try if it's a single mailbox:
  Mailbox maybeMailbox;
  const char * oldscursor = scursor;
  if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
    // yes, it is:
Laurent Montel's avatar
Laurent Montel committed
890
    result.displayName.clear();
891
892
893
894
895
896
897
898
899
900
    result.mailboxList.append( maybeMailbox );
    return true;
  }
  scursor = oldscursor;

  // KAlarm: Allow a local user name to be specified
  // no, it's not a single mailbox. Try if it's a local user name:
  QString maybeUserName;
  if ( parseUserName( scursor, send, maybeUserName, isCRLF ) ) {
    // yes, it is:
Volker Krause's avatar
Volker Krause committed
901
902
903
904
905
    maybeMailbox.setName( QString() );
    AddrSpec addrSpec;
    addrSpec.localPart = maybeUserName;
    addrSpec.domain.clear();
    maybeMailbox.setAddress( addrSpec );
Laurent Montel's avatar
Laurent Montel committed
906
    result.displayName.clear();
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
    result.mailboxList.append( maybeMailbox );
    return true;
  }
  scursor = oldscursor;

  Address maybeAddress;

  // no, it's not a single mailbox. Try if it's a group:
  if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) )
  {
    scursor = oldscursor;   // KAlarm: reinstate original scursor on error return
    return false;
  }

  result = maybeAddress;
  return true;
}

} // namespace HeaderParsing
926
927

// vim: et sw=4: