1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2021 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "kemailclientlauncherjob.h"
9
10#include <KApplicationTrader>
11#include <KConfigGroup>
12#include <KLocalizedString>
13#include <KMacroExpander>
14#include <KService>
15#include <KSharedConfig>
16#include <KShell>
17#include <QProcessEnvironment>
18#include <QUrlQuery>
19
20#include "desktopexecparser.h"
21#include <KIO/ApplicationLauncherJob>
22#include <KIO/CommandLauncherJob>
23
24#ifdef Q_OS_WIN
25#include <windows.h> // Must be included before shellapi.h
26
27#include <shellapi.h>
28#endif
29
30class KEMailClientLauncherJobPrivate
31{
32public:
33 QStringList m_to;
34 QStringList m_cc;
35 QStringList m_bcc;
36 QString m_subject;
37 QString m_body;
38 QList<QUrl> m_attachments;
39
40 QByteArray m_startupId;
41};
42
43KEMailClientLauncherJob::KEMailClientLauncherJob(QObject *parent)
44 : KJob(parent)
45 , d(new KEMailClientLauncherJobPrivate)
46{
47}
48
49KEMailClientLauncherJob::~KEMailClientLauncherJob() = default;
50
51void KEMailClientLauncherJob::setTo(const QStringList &to)
52{
53 d->m_to = to;
54}
55
56void KEMailClientLauncherJob::setCc(const QStringList &cc)
57{
58 d->m_cc = cc;
59}
60
61void KEMailClientLauncherJob::setBcc(const QStringList &bcc)
62{
63 d->m_bcc = bcc;
64}
65
66void KEMailClientLauncherJob::setSubject(const QString &subject)
67{
68 d->m_subject = subject;
69}
70
71void KEMailClientLauncherJob::setBody(const QString &body)
72{
73 d->m_body = body;
74}
75
76void KEMailClientLauncherJob::setAttachments(const QList<QUrl> &urls)
77{
78 d->m_attachments = urls;
79}
80
81void KEMailClientLauncherJob::setStartupId(const QByteArray &startupId)
82{
83 d->m_startupId = startupId;
84}
85
86void KEMailClientLauncherJob::start()
87{
88#ifndef Q_OS_WIN
89 KService::Ptr service = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/mailto"));
90 if (!service) {
91 setError(KJob::UserDefinedError);
92 setErrorText(i18n("No mail client found"));
93 emitDelayedResult();
94 return;
95 }
96 const QString entryPath = service->entryPath().toLower();
97 if (entryPath.contains(s: QLatin1String("thunderbird")) || entryPath.contains(s: QLatin1String("dovecot"))) {
98 const QString exec = KIO::DesktopExecParser::executableName(execLine: service->exec());
99 auto *subjob = new KIO::CommandLauncherJob(exec, thunderbirdArguments(), this);
100 subjob->setStartupId(d->m_startupId);
101 connect(sender: subjob, signal: &KJob::result, context: this, slot: &KEMailClientLauncherJob::emitResult);
102 subjob->start();
103 } else {
104 auto *subjob = new KIO::ApplicationLauncherJob(service, this);
105 subjob->setUrls({mailToUrl()});
106 subjob->setStartupId(d->m_startupId);
107 connect(sender: subjob, signal: &KJob::result, context: this, slot: &KEMailClientLauncherJob::emitResult);
108 subjob->start();
109 }
110#else
111 const QString url = mailToUrl().toString();
112 const QString sOpen = QStringLiteral("open");
113 ShellExecuteW(0, (LPCWSTR)sOpen.utf16(), (LPCWSTR)url.utf16(), 0, 0, SW_NORMAL);
114 emitDelayedResult();
115#endif
116}
117
118void KEMailClientLauncherJob::emitDelayedResult()
119{
120 // Use delayed invocation so the caller has time to connect to the signal
121 QMetaObject::invokeMethod(object: this, function: &KEMailClientLauncherJob::emitResult, type: Qt::QueuedConnection);
122}
123
124QUrl KEMailClientLauncherJob::mailToUrl() const
125{
126 QUrl url;
127 QUrlQuery query;
128 for (const QString &to : std::as_const(t&: d->m_to)) {
129 if (url.path().isEmpty()) {
130 url.setPath(path: to);
131 } else {
132 query.addQueryItem(QStringLiteral("to"), value: to);
133 }
134 }
135 for (const QString &cc : std::as_const(t&: d->m_cc)) {
136 query.addQueryItem(QStringLiteral("cc"), value: cc);
137 }
138 for (const QString &bcc : std::as_const(t&: d->m_bcc)) {
139 query.addQueryItem(QStringLiteral("bcc"), value: bcc);
140 }
141 for (const QUrl &url : std::as_const(t&: d->m_attachments)) {
142 query.addQueryItem(QStringLiteral("attach"), value: url.toString());
143 }
144 if (!d->m_subject.isEmpty()) {
145 query.addQueryItem(QStringLiteral("subject"), value: d->m_subject);
146 }
147 if (!d->m_body.isEmpty()) {
148 query.addQueryItem(QStringLiteral("body"), value: d->m_body);
149 }
150 url.setQuery(query);
151 if (!url.path().isEmpty() || url.hasQuery()) {
152 url.setScheme(QStringLiteral("mailto"));
153 }
154 return url;
155}
156
157QStringList KEMailClientLauncherJob::thunderbirdArguments() const
158{
159 // Thunderbird supports mailto URLs, but refuses attachments for security reasons
160 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1613425)
161 // It however supports a "command-line" syntax (also used by xdg-email)
162 // which includes attachments.
163 QString arg;
164 const QChar quote = QLatin1Char('\'');
165 auto addString = [&](const char *token, const QString &str) {
166 if (!str.isEmpty()) {
167 arg += QLatin1String(token) + quote + str + quote;
168 }
169 };
170 auto addList = [&](const char *token, const QStringList &list) {
171 if (!list.isEmpty()) {
172 arg += QLatin1String(token) + quote + list.join(sep: QLatin1Char(',')) + quote;
173 }
174 };
175 addList(",to=", d->m_to);
176 addList(",cc=", d->m_cc);
177 addList(",bcc=", d->m_bcc);
178 addList(",attachment=", QUrl::toStringList(uris: d->m_attachments));
179 addString(",subject=", d->m_subject);
180 addString(",body=", d->m_body);
181
182 QStringList resultArgs{QLatin1String("-compose")};
183 if (!arg.isEmpty()) {
184 resultArgs.push_back(t: arg.mid(position: 1)); // remove first comma
185 }
186 return resultArgs;
187}
188
189#include "moc_kemailclientlauncherjob.cpp"
190

source code of kio/src/gui/kemailclientlauncherjob.cpp