Libksieve

sievejob.cpp
1 /* -*- c++ -*-
2  sievejob.h
3 
4  SPDX-FileCopyrightText: 2002 Marc Mutz <[email protected]>
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 
17 using namespace KManageSieve;
18 
19 QHash<QUrl, QPointer<Session>> SieveJob::Private::m_sessionPool;
20 
21 Session *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 
38 static 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 
58 void 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 
107 bool 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 
223 void 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 
233 SieveJob::SieveJob(QObject *parent)
234  : QObject(parent)
235  , d(new Private(this))
236 {
237 }
238 
239 SieveJob::~SieveJob()
240 {
241  kill();
242 
243  delete d;
244 }
245 
246 void 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 
254 QStringList SieveJob::sieveCapabilities() const
255 {
256  Session *session = d->sessionForUrl(d->mUrl);
257  if (session) {
258  return session->sieveExtensions();
259  }
260  return QStringList();
261 }
262 
263 bool SieveJob::fileExists() const
264 {
265  return d->mFileExists;
266 }
267 
268 QString SieveJob::errorString() const
269 {
270  return d->mErrorMessage;
271 }
272 
273 void SieveJob::setErrorMessage(const QString &str)
274 {
275  d->mErrorMessage = str;
276 }
277 
278 SieveJob *SieveJob::put(const QUrl &destination, const QString &script, bool makeActive, bool wasActive)
279 {
280  QStack<Private::Command> commands;
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 
300 SieveJob *SieveJob::get(const QUrl &source)
301 {
302  QStack<Private::Command> commands;
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 
314 SieveJob *SieveJob::list(const QUrl &source)
315 {
316  QStack<Private::Command> commands;
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 
327 SieveJob *SieveJob::del(const QUrl &url)
328 {
329  QStack<Private::Command> commands;
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 
340 SieveJob *SieveJob::deactivate(const QUrl &url)
341 {
342  QStack<Private::Command> commands;
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 
352 SieveJob *SieveJob::rename(const QUrl &url, const QString &newName)
353 {
354  QStack<Private::Command> commands;
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 
366 SieveJob *SieveJob::check(const QUrl &url, const QString &script)
367 {
368  QStack<Private::Command> commands;
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 
380 SieveJob *SieveJob::activate(const QUrl &url)
381 {
382  QStack<Private::Command> commands;
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 }
QString & append(QChar ch)
T * data() const const
void push(const T &t)
bool isEmpty() const const
void clear()
void resize(int size)
QString fromUtf8(const char *str, int size)
QByteArray number(int n, int base)
QByteArray::iterator begin()
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & end()
KIOWIDGETS_EXPORT bool run(const QUrl &_url, bool _is_local)
A response from a managesieve server.
Definition: response.h:17
int size() const const
A job to manage sieve scripts.
Definition: sievejob.h:37
QByteArray::iterator end()
QByteArray toUtf8() const const
A network session with a manage sieve server.
Definition: session.h:34
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Apr 10 2021 23:09:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.