Libksieve

sievejob.cpp
1/* -*- c++ -*-
2 sievejob.h
3
4 SPDX-FileCopyrightText: 2002 Marc Mutz <mutz@kde.org>
5
6 SPDX-License-Identifier: GPL-2.0-only
7*/
8
9#include "sievejob.h"
10#include "session.h"
11#include "sievejob_p.h"
12
13#include "kmanagersieve_debug.h"
14#include <KLocalizedString>
15#include <QPointer>
16
17using namespace KManageSieve;
18
19QHash<QUrl, QPointer<Session>> SieveJob::Private::m_sessionPool;
20
21Session *SieveJob::Private::sessionForUrl(const QUrl &url)
22{
23 QUrl hostUrl(url);
24 hostUrl.setPath(QString()); // remove parts not required to identify the server
25 QPointer<Session> sessionPtr = m_sessionPool.value(hostUrl);
26 if (!sessionPtr) {
27 sessionPtr = QPointer<Session>(new Session());
28 m_sessionPool.insert(hostUrl, sessionPtr);
29 sessionPtr->connectToHost(hostUrl);
30 } else {
31 if (sessionPtr->disconnected()) {
32 sessionPtr->connectToHost(hostUrl);
33 }
34 }
35 return sessionPtr.data();
36}
37
38static void append_lf2crlf(QByteArray &out, const QByteArray &in)
39{
40 if (in.isEmpty()) {
41 return;
42 }
43 const unsigned int oldOutSize = out.size();
44 out.resize(oldOutSize + 2 * in.size());
45 const char *s = in.begin();
46 const char *const end = in.end();
47 char *d = out.begin() + oldOutSize;
48 char last = '\0';
49 while (s < end) {
50 if (*s == '\n' && last != '\r') {
51 *d++ = '\r';
52 }
53 *d++ = last = *s++;
54 }
55 out.resize(d - out.begin());
56}
57
58void SieveJob::Private::run(Session *session)
59{
60 switch (mCommands.top()) {
61 case Get: {
62 const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
63 session->sendData("GETSCRIPT \"" + filename.toUtf8() + "\"");
64 break;
65 }
66 case Put: {
67 const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
68 QByteArray encodedData;
69 append_lf2crlf(encodedData, mScript.toUtf8());
70 session->sendData("PUTSCRIPT \"" + filename.toUtf8() + "\" {" + QByteArray::number(encodedData.size()) + "+}");
71 session->sendData(encodedData);
72 break;
73 }
74 case Activate: {
75 const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
76 session->sendData("SETACTIVE \"" + filename.toUtf8() + "\"");
77 break;
78 }
79 case Deactivate:
80 session->sendData("SETACTIVE \"\"");
81 break;
82 case List:
83 case SearchActive:
84 session->sendData("LISTSCRIPTS");
85 break;
86 case Delete: {
87 const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
88 session->sendData("DELETESCRIPT \"" + filename.toUtf8() + "\"");
89 break;
90 }
91 case Rename: {
92 const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
93 const QByteArray ba = QByteArray("RENAMESCRIPT \"" + filename.toUtf8() + "\" \"" + mNewName.toUtf8() + "\"");
94 session->sendData(ba);
95 break;
96 }
97 case Check: {
98 QByteArray encodedData;
99 append_lf2crlf(encodedData, mScript.toUtf8());
100 session->sendData("RENAMESCRIPT {" + QByteArray::number(encodedData.size()) + "+}");
101 session->sendData(encodedData);
102 break;
103 }
104 }
105}
106
107bool SieveJob::Private::handleResponse(const Response &response, const QByteArray &data)
108{
109 if (mCommands.isEmpty()) {
110 return false;
111 }
112 const Command lastCmd = mCommands.top();
113
114 QString errMsg;
115 // handle non-action responses
116 if (response.type() != Response::Action) {
117 switch (lastCmd) {
118 case Get:
119 mScript = QString::fromUtf8(data);
120 break;
121 case List:
122 case SearchActive: {
123 const QString filename = QString::fromUtf8(response.key());
124 mAvailableScripts.append(filename);
125 const bool isActive = response.extra() == "ACTIVE";
126
127 if (isActive) {
128 mActiveScriptName = filename;
129 }
130
131 if (mFileExists == DontKnow && filename == mUrl.fileName()) {
132 mFileExists = Yes;
133 }
134
135 Q_EMIT q->item(q, filename, isActive);
136 break;
137 }
138 case Put:
139 if (response.type() == Response::KeyValuePair) {
140 errMsg = QString::fromUtf8(response.key());
141 mErrorMessage = i18n(
142 "The script did not upload successfully.\n"
143 "This is probably due to errors in the script.\n"
144 "The server responded:\n%1",
145 errMsg);
146 } else if (response.type() == Response::Quantity) {
147 errMsg = QString::fromUtf8(data);
148 mErrorMessage = i18n(
149 "The script did not upload successfully.\n"
150 "This is probably due to errors in the script.\n"
151 "The server responded:\n%1",
152 errMsg);
153 } else {
154 mErrorMessage = i18n("The script did not upload successfully.\nThe script may contain errors.");
155 }
156 break;
157 default:
158 qCDebug(KMANAGERSIEVE_LOG) << "Unhandled response: " << response.key() << response.value() << response.extra() << data;
159 }
160 if (lastCmd != Put) {
161 return false; // we expect more
162 }
163 }
164
165 // First, let's see if we come back from a SearchActive. If so, set
166 // mFileExists to No if we didn't see the mUrl.fileName() during
167 // listDir...
168 if (lastCmd == SearchActive && mFileExists == DontKnow && response.operationSuccessful()) {
169 mFileExists = No;
170 }
171
172 // prepare for next round:
173 mCommands.pop();
174 // check for errors:
175 if (!response.operationSuccessful()) {
176 if (mErrorMessage.isEmpty()) {
177 if (!data.isEmpty()) {
178 // the error message can be in multiple lines after NO {123}, then it's in 'data'.
179 mErrorMessage = QString::fromUtf8(data);
180 } else {
181 // or the error message is on the line of the NO
182 mErrorMessage = QString::fromUtf8(response.key());
183 }
184 }
185 Q_EMIT q->result(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
186
187 if (lastCmd == List) {
188 Q_EMIT q->gotList(q, false, mAvailableScripts, mActiveScriptName);
189 } else {
190 Q_EMIT q->gotScript(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
191 }
192 q->deleteLater();
193 return true;
194 }
195
196 // check for new tasks:
197 if (!mCommands.empty()) {
198 // Don't fail getting a non-existent script:
199 if ((mCommands.top() == Get) && (mFileExists == No)) {
200 mScript.clear();
201 mCommands.pop();
202 }
203 }
204
205 if (mCommands.empty()) {
206 // was last command; report success and delete this object:
207 Q_EMIT q->result(q, true, mScript, (mUrl.fileName() == mActiveScriptName));
208 if (lastCmd == List) {
209 Q_EMIT q->gotList(q, true, mAvailableScripts, mActiveScriptName);
210 } else {
211 Q_EMIT q->gotScript(q, true, mScript, (mUrl.fileName() == mActiveScriptName));
212 }
213
214 q->deleteLater();
215 return true;
216 } else {
217 // schedule the next command:
218 run(sessionForUrl(mUrl));
219 return false;
220 }
221}
222
223void SieveJob::Private::killed()
224{
225 Q_EMIT q->result(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
226 if (mCommands.top() == List) {
227 Q_EMIT q->gotList(q, false, mAvailableScripts, mActiveScriptName);
228 } else {
229 Q_EMIT q->gotScript(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
230 }
231}
232
233SieveJob::SieveJob(QObject *parent)
234 : QObject(parent)
235 , d(new Private(this))
236{
237}
238
239SieveJob::~SieveJob()
240{
241 kill();
242
243 delete d;
244}
245
246void SieveJob::kill(KJob::KillVerbosity verbosity)
247{
248 if (d->mCommands.isEmpty()) {
249 return; // done already
250 }
251 Private::sessionForUrl(d->mUrl)->killJob(this, verbosity);
252}
253
255{
256 Session *session = d->sessionForUrl(d->mUrl);
257 if (session) {
258 return session->sieveExtensions();
259 }
260 return {};
261}
262
264{
265 return d->mFileExists;
266}
267
269{
270 return d->mErrorMessage;
271}
272
273void SieveJob::setErrorMessage(const QString &str)
274{
275 d->mErrorMessage = str;
276}
277
278SieveJob *SieveJob::put(const QUrl &destination, const QString &script, bool makeActive, bool wasActive)
279{
281 if (makeActive) {
282 commands.push(Private::Activate);
283 }
284
285 if (wasActive) {
286 commands.push(Private::Deactivate);
287 }
288
289 commands.push(Private::Put);
290
291 auto job = new SieveJob;
292 job->d->mUrl = destination;
293 job->d->mScript = script;
294 job->d->mCommands = commands;
295
296 Private::sessionForUrl(destination)->scheduleJob(job);
297 return job;
298}
299
301{
303 commands.push(Private::Get);
304 commands.push(Private::SearchActive);
305
306 auto job = new SieveJob;
307 job->d->mUrl = source;
308 job->d->mCommands = commands;
309
310 Private::sessionForUrl(source)->scheduleJob(job);
311 return job;
312}
313
315{
317 commands.push(Private::List);
318
319 auto job = new SieveJob;
320 job->d->mUrl = source;
321 job->d->mCommands = commands;
322
323 Private::sessionForUrl(source)->scheduleJob(job);
324 return job;
325}
326
328{
330 commands.push(Private::Delete);
331
332 auto job = new SieveJob;
333 job->d->mUrl = url;
334 job->d->mCommands = commands;
335
336 Private::sessionForUrl(url)->scheduleJob(job);
337 return job;
338}
339
341{
343 commands.push(Private::Deactivate);
344 auto job = new SieveJob;
345 job->d->mUrl = url;
346 job->d->mCommands = commands;
347
348 Private::sessionForUrl(url)->scheduleJob(job);
349 return job;
350}
351
352SieveJob *SieveJob::rename(const QUrl &url, const QString &newName)
353{
355 commands.push(Private::Rename);
356
357 auto job = new SieveJob;
358 job->d->mUrl = url;
359 job->d->mNewName = newName;
360 job->d->mCommands = commands;
361
362 Private::sessionForUrl(url)->scheduleJob(job);
363 return job;
364}
365
366SieveJob *SieveJob::check(const QUrl &url, const QString &script)
367{
369 commands.push(Private::Check);
370
371 auto job = new SieveJob;
372 job->d->mUrl = url;
373 job->d->mScript = script;
374 job->d->mCommands = commands;
375
376 Private::sessionForUrl(url)->scheduleJob(job);
377 return job;
378}
379
381{
383 commands.push(Private::Activate);
384
385 auto job = new SieveJob;
386 job->d->mUrl = url;
387 job->d->mCommands = commands;
388
389 Private::sessionForUrl(url)->scheduleJob(job);
390 return job;
391}
392
393#include "moc_sievejob.cpp"
A response from a managesieve server.
Definition response.h:18
A network session with a manage sieve server.
Definition session.h:35
A job to manage sieve scripts.
Definition sievejob.h:31
void kill(KJob::KillVerbosity verbosity=KJob::Quietly)
Kills the sieve job.
Definition sievejob.cpp:246
static SieveJob * deactivate(const QUrl &url)
Deactivates the script with the given sieve url.
Definition sievejob.cpp:340
bool fileExists() const
Returns whether the requested sieve script exists on the IMAP server.
Definition sievejob.cpp:263
static SieveJob * check(const QUrl &url, const QString &script)
Check the script with the given sieve url.
Definition sievejob.cpp:366
QString errorString() const
A human-readable error message.
Definition sievejob.cpp:268
QStringList sieveCapabilities() const
Returns the sieve capabilities of the IMAP server.
Definition sievejob.cpp:254
static SieveJob * list(const QUrl &url)
Lists all available scripts at the given sieve url.
Definition sievejob.cpp:314
static SieveJob * put(const QUrl &destination, const QString &script, bool makeActive, bool wasActive)
Stores a sieve script on an IMAP server.
Definition sievejob.cpp:278
static SieveJob * del(const QUrl &url)
Deletes the script with the given sieve url.
Definition sievejob.cpp:327
static SieveJob * rename(const QUrl &url, const QString &newName)
Rename the script with the given sieve url and new name newName.
Definition sievejob.cpp:352
static SieveJob * get(const QUrl &source)
Gets a sieve script from an IMAP server.
Definition sievejob.cpp:300
static SieveJob * activate(const QUrl &url)
Activates the script with the given sieve url.
Definition sievejob.cpp:380
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & end()
iterator begin()
iterator end()
bool isEmpty() const const
QByteArray number(double n, char format, int precision)
void resize(qsizetype newSize, char c)
qsizetype size() const const
T * data() const const
void push(const T &t)
QString fromUtf8(QByteArrayView str)
QByteArray toUtf8() const const
QFuture< T > run(Function function,...)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:19 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.