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 "sievejob_p.h"
11 #include "session.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  {
63  const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
64  session->sendData("GETSCRIPT \"" + filename.toUtf8() + "\"");
65  break;
66  }
67  case Put:
68  {
69  const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
70  QByteArray encodedData;
71  append_lf2crlf(encodedData, mScript.toUtf8());
72  session->sendData("PUTSCRIPT \"" + filename.toUtf8() + "\" {" + QByteArray::number(encodedData.size()) + "+}");
73  session->sendData(encodedData);
74  break;
75  }
76  case Activate:
77  {
78  const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
79  session->sendData("SETACTIVE \"" + filename.toUtf8() + "\"");
80  break;
81  }
82  case Deactivate:
83  session->sendData("SETACTIVE \"\"");
84  break;
85  case List:
86  case SearchActive:
87  session->sendData("LISTSCRIPTS");
88  break;
89  case Delete:
90  {
91  const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
92  session->sendData("DELETESCRIPT \"" + filename.toUtf8() + "\"");
93  break;
94  }
95  case Rename:
96  {
97  const QString filename = mUrl.fileName(/*QUrl::ObeyTrailingSlash*/);
98  const QByteArray ba = QByteArray("RENAMESCRIPT \"" + filename.toUtf8() + "\" \"" + mNewName.toUtf8() + "\"");
99  session->sendData(ba);
100  break;
101  }
102  case Check:
103  {
104  QByteArray encodedData;
105  append_lf2crlf(encodedData, mScript.toUtf8());
106  session->sendData("RENAMESCRIPT {" + QByteArray::number(encodedData.size()) + "+}");
107  session->sendData(encodedData);
108  break;
109  }
110  }
111 }
112 
113 bool SieveJob::Private::handleResponse(const Response &response, const QByteArray &data)
114 {
115  if (mCommands.isEmpty()) {
116  return false;
117  }
118  const Command lastCmd = mCommands.top();
119 
120  QString errMsg;
121  // handle non-action responses
122  if (response.type() != Response::Action) {
123  switch (lastCmd) {
124  case Get:
125  mScript = QString::fromUtf8(data);
126  break;
127  case List:
128  case SearchActive:
129  {
130  const QString filename = QString::fromUtf8(response.key());
131  mAvailableScripts.append(filename);
132  const bool isActive = response.extra() == "ACTIVE";
133 
134  if (isActive) {
135  mActiveScriptName = filename;
136  }
137 
138  if (mFileExists == DontKnow && filename == mUrl.fileName()) {
139  mFileExists = Yes;
140  }
141 
142  Q_EMIT q->item(q, filename, isActive);
143  break;
144  }
145  case Put:
146  if (response.type() == Response::KeyValuePair) {
147  errMsg = QString::fromUtf8(response.key());
148  mErrorMessage = i18n("The script did not upload successfully.\n"
149  "This is probably due to errors in the script.\n"
150  "The server responded:\n%1", errMsg);
151  } else if (response.type() == Response::Quantity) {
152  errMsg = QString::fromUtf8(data);
153  mErrorMessage = i18n("The script did not upload successfully.\n"
154  "This is probably due to errors in the script.\n"
155  "The server responded:\n%1", errMsg);
156  } else {
157  mErrorMessage = i18n("The script did not upload successfully.\nThe script may contain errors.");
158  }
159  break;
160  default:
161  qCDebug(KMANAGERSIEVE_LOG) << "Unhandled response: " << response.key() << response.value() << response.extra() << data;
162  }
163  if (lastCmd != Put) {
164  return false; // we expect more
165  }
166  }
167 
168  // First, let's see if we come back from a SearchActive. If so, set
169  // mFileExists to No if we didn't see the mUrl.fileName() during
170  // listDir...
171  if (lastCmd == SearchActive && mFileExists == DontKnow && response.operationSuccessful()) {
172  mFileExists = No;
173  }
174 
175  // prepare for next round:
176  mCommands.pop();
177  // check for errors:
178  if (!response.operationSuccessful()) {
179  if (mErrorMessage.isEmpty()) {
180  if (!data.isEmpty()) {
181  // the error message can be in multiple lines after NO {123}, then it's in 'data'.
182  mErrorMessage = QString::fromUtf8(data);
183  } else {
184  // or the error message is on the line of the NO
185  mErrorMessage = QString::fromUtf8(response.key());
186  }
187  }
188  Q_EMIT q->result(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
189 
190  if (lastCmd == List) {
191  Q_EMIT q->gotList(q, false, mAvailableScripts, mActiveScriptName);
192  } else {
193  Q_EMIT q->gotScript(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
194  }
195  q->deleteLater();
196  return true;
197  }
198 
199  // check for new tasks:
200  if (!mCommands.empty()) {
201  // Don't fail getting a non-existent script:
202  if ((mCommands.top() == Get) && (mFileExists == No)) {
203  mScript.clear();
204  mCommands.pop();
205  }
206  }
207 
208  if (mCommands.empty()) {
209  // was last command; report success and delete this object:
210  Q_EMIT q->result(q, true, mScript, (mUrl.fileName() == mActiveScriptName));
211  if (lastCmd == List) {
212  Q_EMIT q->gotList(q, true, mAvailableScripts, mActiveScriptName);
213  } else {
214  Q_EMIT q->gotScript(q, true, mScript, (mUrl.fileName() == mActiveScriptName));
215  }
216 
217  q->deleteLater();
218  return true;
219  } else {
220  // schedule the next command:
221  run(sessionForUrl(mUrl));
222  return false;
223  }
224 }
225 
226 void SieveJob::Private::killed()
227 {
228  Q_EMIT q->result(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
229  if (mCommands.top() == List) {
230  Q_EMIT q->gotList(q, false, mAvailableScripts, mActiveScriptName);
231  } else {
232  Q_EMIT q->gotScript(q, false, mScript, (mUrl.fileName() == mActiveScriptName));
233  }
234 }
235 
236 SieveJob::SieveJob(QObject *parent)
237  : QObject(parent)
238  , d(new Private(this))
239 {
240 }
241 
242 SieveJob::~SieveJob()
243 {
244  kill();
245 
246  delete d;
247 }
248 
249 void SieveJob::kill(KJob::KillVerbosity verbosity)
250 {
251  if (d->mCommands.isEmpty()) {
252  return; // done already
253  }
254  Private::sessionForUrl(d->mUrl)->killJob(this, verbosity);
255 }
256 
257 QStringList SieveJob::sieveCapabilities() const
258 {
259  Session *session = d->sessionForUrl(d->mUrl);
260  if (session) {
261  return session->sieveExtensions();
262  }
263  return QStringList();
264 }
265 
266 bool SieveJob::fileExists() const
267 {
268  return d->mFileExists;
269 }
270 
271 QString SieveJob::errorString() const
272 {
273  return d->mErrorMessage;
274 }
275 
276 void SieveJob::setErrorMessage(const QString &str)
277 {
278  d->mErrorMessage = str;
279 }
280 
281 SieveJob *SieveJob::put(const QUrl &destination, const QString &script, bool makeActive, bool wasActive)
282 {
283  QStack<Private::Command> commands;
284  if (makeActive) {
285  commands.push(Private::Activate);
286  }
287 
288  if (wasActive) {
289  commands.push(Private::Deactivate);
290  }
291 
292  commands.push(Private::Put);
293 
294  SieveJob *job = new SieveJob;
295  job->d->mUrl = destination;
296  job->d->mScript = script;
297  job->d->mCommands = commands;
298 
299  Private::sessionForUrl(destination)->scheduleJob(job);
300  return job;
301 }
302 
303 SieveJob *SieveJob::get(const QUrl &source)
304 {
305  QStack<Private::Command> commands;
306  commands.push(Private::Get);
307  commands.push(Private::SearchActive);
308 
309  SieveJob *job = new SieveJob;
310  job->d->mUrl = source;
311  job->d->mCommands = commands;
312 
313  Private::sessionForUrl(source)->scheduleJob(job);
314  return job;
315 }
316 
317 SieveJob *SieveJob::list(const QUrl &source)
318 {
319  QStack<Private::Command> commands;
320  commands.push(Private::List);
321 
322  SieveJob *job = new SieveJob;
323  job->d->mUrl = source;
324  job->d->mCommands = commands;
325 
326  Private::sessionForUrl(source)->scheduleJob(job);
327  return job;
328 }
329 
330 SieveJob *SieveJob::del(const QUrl &url)
331 {
332  QStack<Private::Command> commands;
333  commands.push(Private::Delete);
334 
335  SieveJob *job = new SieveJob;
336  job->d->mUrl = url;
337  job->d->mCommands = commands;
338 
339  Private::sessionForUrl(url)->scheduleJob(job);
340  return job;
341 }
342 
343 SieveJob *SieveJob::deactivate(const QUrl &url)
344 {
345  QStack<Private::Command> commands;
346  commands.push(Private::Deactivate);
347  SieveJob *job = new SieveJob;
348  job->d->mUrl = url;
349  job->d->mCommands = commands;
350 
351  Private::sessionForUrl(url)->scheduleJob(job);
352  return job;
353 }
354 
355 SieveJob *SieveJob::rename(const QUrl &url, const QString &newName)
356 {
357  QStack<Private::Command> commands;
358  commands.push(Private::Rename);
359 
360  SieveJob *job = new SieveJob;
361  job->d->mUrl = url;
362  job->d->mNewName = newName;
363  job->d->mCommands = commands;
364 
365  Private::sessionForUrl(url)->scheduleJob(job);
366  return job;
367 }
368 
369 SieveJob *SieveJob::check(const QUrl &url, const QString &script)
370 {
371  QStack<Private::Command> commands;
372  commands.push(Private::Check);
373 
374  SieveJob *job = new SieveJob;
375  job->d->mUrl = url;
376  job->d->mScript = script;
377  job->d->mCommands = commands;
378 
379  Private::sessionForUrl(url)->scheduleJob(job);
380  return job;
381 }
382 
383 SieveJob *SieveJob::activate(const QUrl &url)
384 {
385  QStack<Private::Command> commands;
386  commands.push(Private::Activate);
387 
388  SieveJob *job = new SieveJob;
389  job->d->mUrl = url;
390  job->d->mCommands = commands;
391 
392  Private::sessionForUrl(url)->scheduleJob(job);
393  return job;
394 }
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:36
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-2020 The KDE developers.
Generated on Sat Oct 24 2020 23:14:15 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.