• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.5 API Reference
  • KDE Home
  • Contact Us
 

kioslave/imap4

  • kioslave
  • imap4
imap4.cpp
1 /**********************************************************************
2  *
3  * imap4.cc - IMAP4rev1 KIOSlave
4  * Copyright (C) 2001-2002 Michael Haeckel <haeckel@kde.org>
5  * Copyright (C) 1999 John Corey <jcorey@fruity.ath.cx>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  * Send comments and bug fixes to jcorey@fruity.ath.cx
22  *
23  *********************************************************************/
24 
59 #include "imap4.h"
60 
61 #include <QByteArray>
62 #include <QList>
63 
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <signal.h>
67 #include <sys/stat.h>
68 #include <sys/types.h>
69 #include <sys/wait.h>
70 #include <errno.h>
71 
72 extern "C" {
73 #include <sasl/sasl.h>
74 }
75 
76 #include <qbuffer.h>
77 #include <qdatetime.h>
78 #include <QRegExp>
79 #include <kprotocolmanager.h>
80 #include <kcomponentdata.h>
81 #include <kmessagebox.h>
82 #include <kdebug.h>
83 #include <kio/connection.h>
84 #include <kio/slaveinterface.h>
85 #include <klocale.h>
86 #include <kmimetype.h>
87 #include <kcodecs.h>
88 #include <kde_file.h>
89 
90 #include "common.h"
91 #include "kdemacros.h"
92 
93 #define IMAP_PROTOCOL "imap"
94 #define IMAP_SSL_PROTOCOL "imaps"
95 const int ImapPort = 143;
96 const int ImapsPort = 993;
97 
98 using namespace KIO;
99 
100 extern "C"
101 {
102  void sigalrm_handler (int);
103  KDE_EXPORT int kdemain (int argc, char **argv);
104 }
105 
106 int
107 kdemain (int argc, char **argv)
108 {
109  kDebug(7116) <<"IMAP4::kdemain";
110 
111  KComponentData instance ("kio_imap4");
112  if (argc != 4)
113  {
114  fprintf(stderr, "Usage: kio_imap4 protocol domain-socket1 domain-socket2\n");
115  ::exit (-1);
116  }
117 
118  if (!initSASL())
119  ::exit(-1);
120 
121  //set debug handler
122 
123  IMAP4Protocol *slave;
124  if (strcasecmp (argv[1], IMAP_SSL_PROTOCOL) == 0)
125  slave = new IMAP4Protocol (argv[2], argv[3], true);
126  else if (strcasecmp (argv[1], IMAP_PROTOCOL) == 0)
127  slave = new IMAP4Protocol (argv[2], argv[3], false);
128  else
129  abort ();
130  slave->dispatchLoop ();
131  delete slave;
132 
133  sasl_done();
134 
135  return 0;
136 }
137 
138 void
139 sigchld_handler (int signo)
140 {
141  // A signal handler that calls for example waitpid has to save errno
142  // before and restore it afterwards.
143  // (cf. https://www.securecoding.cert.org/confluence/display/cplusplus/ERR32-CPP.+Do+not+rely+on+indeterminate+values+of+errno)
144  const int save_errno = errno;
145  int pid, status;
146 
147  while (signo == SIGCHLD)
148  {
149  pid = waitpid (-1, &status, WNOHANG);
150  if (pid <= 0)
151  {
152  // Reinstall signal handler, since Linux resets to default after
153  // the signal occurred ( BSD handles it different, but it should do
154  // no harm ).
155  KDE_signal (SIGCHLD, sigchld_handler);
156  break;
157  }
158  }
159 
160  errno = save_errno;
161 }
162 
163 IMAP4Protocol::IMAP4Protocol (const QByteArray & pool, const QByteArray & app, bool isSSL)
164  :TCPSlaveBase ((isSSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL), pool, app, isSSL),
165  imapParser (),
166  mimeIO (),
167  mySSL( isSSL ),
168  relayEnabled( false ),
169  cacheOutput( false ),
170  decodeContent( false ),
171  outputBuffer(&outputCache),
172  outputBufferIndex(0),
173  mProcessedSize( 0 ),
174  readBufferLen( 0 ),
175  mTimeOfLastNoop( QDateTime() )
176 {
177  readBuffer[0] = 0x00;
178 }
179 
180 IMAP4Protocol::~IMAP4Protocol ()
181 {
182  disconnectFromHost();
183  kDebug(7116) <<"IMAP4: Finishing";
184 }
185 
186 void
187 IMAP4Protocol::get (const KUrl & _url)
188 {
189  if (!makeLogin()) return;
190  kDebug(7116) <<"IMAP4::get -" << _url.prettyUrl();
191  QString aBox, aSequence, aType, aSection, aValidity, aDelimiter, aInfo;
192  enum IMAP_TYPE aEnum =
193  parseURL (_url, aBox, aSection, aType, aSequence, aValidity, aDelimiter, aInfo);
194  if (aEnum != ITYPE_ATTACH)
195  mimeType (getMimeType(aEnum));
196  if (aInfo == "DECODE")
197  decodeContent = true;
198 
199  if (aSequence == "0:0" && getState() == ISTATE_SELECT)
200  {
201  CommandPtr cmd = doCommand (imapCommand::clientNoop());
202  completeQueue.removeAll(cmd);
203  }
204 
205  if (aSequence.isEmpty ())
206  {
207  aSequence = "1:*";
208  }
209 
210  mProcessedSize = 0;
211  CommandPtr cmd;
212  if (!assureBox (aBox, true)) return;
213 
214 #ifdef USE_VALIDITY
215  if (selectInfo.uidValidityAvailable () && !aValidity.isEmpty ()
216  && selectInfo.uidValidity () != aValidity.toULong ())
217  {
218  // this url is stale
219  error (ERR_COULD_NOT_READ, _url.prettyUrl());
220  return;
221  }
222  else
223 #endif
224  {
225  // The "section" specified by the application can be:
226  // * empty (which means body, size and flags)
227  // * a known keyword, like STRUCTURE, ENVELOPE, HEADER, BODY.PEEK[...]
228  // (in which case the slave has some logic to add the necessary items)
229  // * Otherwise, it specifies the exact data items to request. In this case, all
230  // the logic is in the app.
231 
232  QString aUpper = aSection.toUpper();
233  if (aUpper.contains("STRUCTURE"))
234  {
235  aSection = "BODYSTRUCTURE";
236  }
237  else if (aUpper.contains("ENVELOPE"))
238  {
239  aSection = "UID RFC822.SIZE FLAGS ENVELOPE";
240  if (hasCapability("IMAP4rev1")) {
241  aSection += " BODY.PEEK[HEADER.FIELDS (REFERENCES)]";
242  } else {
243  // imap4 does not know HEADER.FIELDS
244  aSection += " RFC822.HEADER.LINES (REFERENCES)";
245  }
246  }
247  else if (aUpper == "HEADER")
248  {
249  aSection = "UID RFC822.HEADER RFC822.SIZE FLAGS";
250  }
251  else if (aUpper.contains("BODY.PEEK["))
252  {
253  if (aUpper.contains("BODY.PEEK[]"))
254  {
255  if (!hasCapability("IMAP4rev1")) // imap4 does not know BODY.PEEK[]
256  aSection.replace("BODY.PEEK[]", "RFC822.PEEK");
257  }
258  aSection.prepend("UID RFC822.SIZE FLAGS ");
259  }
260  else if (aSection.isEmpty())
261  {
262  aSection = "UID BODY[] RFC822.SIZE FLAGS";
263  }
264  if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
265  {
266  // write the digest header
267  cacheOutput = true;
268  outputLine
269  ("Content-Type: multipart/digest; boundary=\"IMAPDIGEST\"\r\n", 55);
270  if (selectInfo.recentAvailable ())
271  outputLineStr ("X-Recent: " +
272  QString::number(selectInfo.recent ()) + "\r\n");
273  if (selectInfo.countAvailable ())
274  outputLineStr ("X-Count: " + QString::number(selectInfo.count ()) +
275  "\r\n");
276  if (selectInfo.unseenAvailable ())
277  outputLineStr ("X-Unseen: " +
278  QString::number(selectInfo.unseen ()) + "\r\n");
279  if (selectInfo.uidValidityAvailable ())
280  outputLineStr ("X-uidValidity: " +
281  QString::number(selectInfo.uidValidity ()) +
282  "\r\n");
283  if (selectInfo.uidNextAvailable ())
284  outputLineStr ("X-UidNext: " +
285  QString::number(selectInfo.uidNext ()) + "\r\n");
286  if (selectInfo.flagsAvailable ())
287  outputLineStr ("X-Flags: " + QString::number(selectInfo.flags ()) +
288  "\r\n");
289  if (selectInfo.permanentFlagsAvailable ())
290  outputLineStr ("X-PermanentFlags: " +
291  QString::number(selectInfo.permanentFlags ()) + "\r\n");
292  if (selectInfo.readWriteAvailable ()) {
293  if (selectInfo.readWrite()) {
294  outputLine ("X-Access: Read/Write\r\n", 22);
295  } else {
296  outputLine ("X-Access: Read only\r\n", 21);
297  }
298  }
299  outputLine ("\r\n", 2);
300  flushOutput(QString());
301  cacheOutput = false;
302  }
303 
304  if (aEnum == ITYPE_MSG || (aEnum == ITYPE_ATTACH && !decodeContent))
305  relayEnabled = true; // normal mode, relay data
306 
307  if (aSequence != "0:0")
308  {
309  QString contentEncoding;
310  if (aEnum == ITYPE_ATTACH && decodeContent)
311  {
312  // get the MIME header and fill getLastHandled()
313  QString mySection = aSection;
314  mySection.replace(']', ".MIME]");
315  cmd = sendCommand (imapCommand::clientFetch (aSequence, mySection));
316  do
317  {
318  while (!parseLoop ()) {}
319  }
320  while (!cmd->isComplete ());
321  completeQueue.removeAll (cmd);
322  // get the content encoding now because getLastHandled will be cleared
323  if (getLastHandled() && getLastHandled()->getHeader())
324  contentEncoding = getLastHandled()->getHeader()->getEncoding();
325 
326  // from here on collect the data
327  // it is send to the client in flushOutput in one go
328  // needed to decode the content
329  cacheOutput = true;
330  }
331 
332  cmd = sendCommand (imapCommand::clientFetch (aSequence, aSection));
333  int res;
334  aUpper = aSection.toUpper();
335  do
336  {
337  while (!(res = parseLoop())) {}
338  if (res == -1) break;
339 
340  mailHeader *lastone = 0;
341  imapCache *cache = getLastHandled ();
342  if (cache)
343  lastone = cache->getHeader ();
344 
345  if (cmd && !cmd->isComplete ())
346  {
347  if ( aUpper.contains("BODYSTRUCTURE")
348  || aUpper.contains("FLAGS")
349  || aUpper.contains("UID")
350  || aUpper.contains("ENVELOPE")
351  || (aUpper.contains("BODY.PEEK[0]")
352  && (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)))
353  {
354  if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
355  {
356  // write the mime header (default is here message/rfc822)
357  outputLine ("--IMAPDIGEST\r\n", 14);
358  cacheOutput = true;
359  if (cache->getUid () != 0)
360  outputLineStr ("X-UID: " +
361  QString::number(cache->getUid ()) + "\r\n");
362  if (cache->getSize () != 0)
363  outputLineStr ("X-Length: " +
364  QString::number(cache->getSize ()) + "\r\n");
365  if (!cache->getDate ().isEmpty())
366  outputLineStr ("X-Date: " + cache->getDate () + "\r\n");
367  if (cache->getFlags () != 0)
368  outputLineStr ("X-Flags: " +
369  QString::number(cache->getFlags ()) + "\r\n");
370  } else cacheOutput = true;
371  if ( lastone && !decodeContent )
372  lastone->outputPart (*this);
373  cacheOutput = false;
374  flushOutput(contentEncoding);
375  }
376  } // if not complete
377  }
378  while (cmd && !cmd->isComplete ());
379  if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX)
380  {
381  // write the end boundary
382  outputLine ("--IMAPDIGEST--\r\n", 16);
383  }
384 
385  completeQueue.removeAll (cmd);
386  }
387  }
388 
389  // just to keep everybody happy when no data arrived
390  data (QByteArray ());
391 
392  finished ();
393  relayEnabled = false;
394  cacheOutput = false;
395  kDebug(7116) <<"IMAP4::get - finished";
396 }
397 
398 void
399 IMAP4Protocol::listDir (const KUrl & _url)
400 {
401  kDebug(7116) <<" IMAP4::listDir -" << _url.prettyUrl();
402 
403  if (_url.path().isEmpty())
404  {
405  KUrl url = _url;
406  url.setPath("/");
407  redirection( url );
408  finished();
409  return;
410  }
411 
412  QString myBox, mySequence, myLType, mySection, myValidity, myDelimiter, myInfo;
413  // parseURL with caching
414  enum IMAP_TYPE myType =
415  parseURL (_url, myBox, mySection, myLType, mySequence, myValidity,
416  myDelimiter, myInfo, true);
417 
418  if (!makeLogin()) return;
419 
420  if (myType == ITYPE_DIR || myType == ITYPE_DIR_AND_BOX)
421  {
422  QString listStr = myBox;
423  CommandPtr cmd;
424 
425  if (!listStr.isEmpty () && !listStr.endsWith(myDelimiter) &&
426  mySection != "FOLDERONLY")
427  listStr += myDelimiter;
428 
429  if (mySection.isEmpty())
430  {
431  listStr += '%';
432  } else if (mySection == "COMPLETE") {
433  listStr += '*';
434  }
435  kDebug(7116) <<"IMAP4Protocol::listDir - listStr=" << listStr;
436  cmd =
437  doCommand (imapCommand::clientList ("", listStr,
438  (myLType == "LSUB" || myLType == "LSUBNOCHECK")));
439  if (cmd->result () == "OK")
440  {
441  QString mailboxName;
442  UDSEntry entry;
443  KUrl aURL = _url;
444  if ( aURL.path().contains(';') )
445  aURL.setPath(aURL.path().left(aURL.path().indexOf(';')));
446 
447  kDebug(7116) <<"IMAP4Protocol::listDir - got" << listResponses.count ();
448 
449  if (myLType == "LSUB")
450  {
451  // fire the same command as LIST to check if the box really exists
452  QList<imapList> listResponsesSave = listResponses;
453  doCommand (imapCommand::clientList ("", listStr, false));
454  for (QList< imapList >::Iterator it = listResponsesSave.begin ();
455  it != listResponsesSave.end (); ++it)
456  {
457  bool boxOk = false;
458  for (QList< imapList >::Iterator it2 = listResponses.begin ();
459  it2 != listResponses.end (); ++it2)
460  {
461  if ((*it2).name() == (*it).name())
462  {
463  boxOk = true;
464  // copy the flags from the LIST-command
465  (*it) = (*it2);
466  break;
467  }
468  }
469  if (boxOk)
470  doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY"));
471  else // this folder is dead
472  kDebug(7116) <<"IMAP4Protocol::listDir - suppress" << (*it).name();
473  }
474  listResponses = listResponsesSave;
475  }
476  else // LIST or LSUBNOCHECK
477  {
478  for (QList< imapList >::Iterator it = listResponses.begin ();
479  it != listResponses.end (); ++it)
480  {
481  doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY"));
482  }
483  }
484  entry.clear ();
485  listEntry (entry, true);
486  }
487  else
488  {
489  error (ERR_CANNOT_ENTER_DIRECTORY, _url.prettyUrl());
490  completeQueue.removeAll (cmd);
491  return;
492  }
493  completeQueue.removeAll (cmd);
494  }
495  if ((myType == ITYPE_BOX || myType == ITYPE_DIR_AND_BOX)
496  && myLType != "LIST" && myLType != "LSUB" && myLType != "LSUBNOCHECK")
497  {
498  KUrl aURL = _url;
499  aURL.setQuery (QString());
500  const QString encodedUrl = aURL.url(KUrl::LeaveTrailingSlash); // utf-8
501 
502  if (!_url.query ().isEmpty ())
503  {
504  QString query = KUrl::fromPercentEncoding (_url.query().toLatin1());
505  query = query.right (query.length () - 1);
506  if (!query.isEmpty())
507  {
508  CommandPtr cmd;
509 
510  if (!assureBox (myBox, true)) return;
511 
512  if (!selectInfo.countAvailable() || selectInfo.count())
513  {
514  cmd = doCommand (imapCommand::clientSearch (query));
515  if (cmd->result() != "OK")
516  {
517  error(ERR_UNSUPPORTED_ACTION, _url.prettyUrl());
518  completeQueue.removeAll (cmd);
519  return;
520  }
521  completeQueue.removeAll (cmd);
522 
523  QStringList list = getResults ();
524  int stretch = 0;
525 
526  if (selectInfo.uidNextAvailable ())
527  stretch = QString::number(selectInfo.uidNext ()).length ();
528  UDSEntry entry;
529  imapCache fake;
530 
531  for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd();
532  ++it)
533  {
534  fake.setUid((*it).toULong());
535  doListEntry (encodedUrl, stretch, &fake);
536  }
537  entry.clear ();
538  listEntry (entry, true);
539  }
540  }
541  }
542  else
543  {
544  if (!assureBox (myBox, true)) return;
545 
546  kDebug(7116) <<"IMAP4: select returned:";
547  if (selectInfo.recentAvailable ())
548  kDebug(7116) <<"Recent:" << selectInfo.recent () <<"d";
549  if (selectInfo.countAvailable ())
550  kDebug(7116) <<"Count:" << selectInfo.count () <<"d";
551  if (selectInfo.unseenAvailable ())
552  kDebug(7116) <<"Unseen:" << selectInfo.unseen () <<"d";
553  if (selectInfo.uidValidityAvailable ())
554  kDebug(7116) <<"uidValidity:" << selectInfo.uidValidity () <<"d";
555  if (selectInfo.flagsAvailable ())
556  kDebug(7116) <<"Flags:" << selectInfo.flags () <<"d";
557  if (selectInfo.permanentFlagsAvailable ())
558  kDebug(7116) <<"PermanentFlags:" << selectInfo.permanentFlags () <<"d";
559  if (selectInfo.readWriteAvailable ())
560  kDebug(7116) <<"Access:" << (selectInfo.readWrite ()?"Read/Write" :"Read only");
561 
562 #ifdef USE_VALIDITY
563  if (selectInfo.uidValidityAvailable ()
564  && selectInfo.uidValidity () != myValidity.toULong ())
565  {
566  //redirect
567  KUrl newUrl = _url;
568 
569  newUrl.setPath ('/' + myBox + ";UIDVALIDITY=" +
570  QString::number(selectInfo.uidValidity ()));
571  kDebug(7116) <<"IMAP4::listDir - redirecting to" << newUrl.prettyUrl();
572  redirection (newUrl);
573 
574 
575  }
576  else
577 #endif
578  if (selectInfo.count () > 0)
579  {
580  int stretch = 0;
581 
582  if (selectInfo.uidNextAvailable ())
583  stretch = QString::number(selectInfo.uidNext ()).length ();
584  // kDebug(7116) << selectInfo.uidNext() <<"d used to stretch" << stretch;
585  UDSEntry entry;
586 
587  if (mySequence.isEmpty()) mySequence = "1:*";
588 
589  bool withSubject = mySection.isEmpty();
590  if (mySection.isEmpty()) mySection = "UID RFC822.SIZE ENVELOPE";
591 
592  bool withFlags = mySection.toUpper().contains("FLAGS") ;
593  CommandPtr fetch =
594  sendCommand (imapCommand::
595  clientFetch (mySequence, mySection));
596  imapCache *cache;
597  do
598  {
599  while (!parseLoop ()) {}
600 
601  cache = getLastHandled ();
602 
603  if (cache && !fetch->isComplete())
604  doListEntry (encodedUrl, stretch, cache, withFlags, withSubject);
605  }
606  while (!fetch->isComplete ());
607  entry.clear ();
608  listEntry (entry, true);
609  }
610  }
611  }
612  if ( !selectInfo.alert().isNull() ) {
613  if ( !myBox.isEmpty() ) {
614  warning( i18n( "Message from %1 while processing '%2': %3", myHost, myBox, selectInfo.alert() ) );
615  } else {
616  warning( i18n( "Message from %1: %2", myHost, selectInfo.alert() ) );
617  }
618  selectInfo.setAlert( 0 );
619  }
620 
621  kDebug(7116) <<"IMAP4Protocol::listDir - Finishing listDir";
622  finished ();
623 }
624 
625 void
626 IMAP4Protocol::setHost (const QString & _host, quint16 _port,
627  const QString & _user, const QString & _pass)
628 {
629  if (myHost != _host || myPort != _port || myUser != _user || myPass != _pass)
630  { // what's the point of doing 4 string compares to avoid 4 string copies?
631  // DF: I guess to avoid calling closeConnection() unnecessarily.
632  if (!myHost.isEmpty ())
633  closeConnection ();
634  myHost = _host;
635  if (_port == 0)
636  myPort = (mySSL) ? ImapsPort : ImapPort;
637  else
638  myPort = _port;
639  myUser = _user;
640  myPass = _pass;
641  }
642 }
643 
644 void
645 IMAP4Protocol::parseRelay (const QByteArray & buffer)
646 {
647  if (relayEnabled) {
648  // relay data immediately
649  data( buffer );
650  mProcessedSize += buffer.size();
651  processedSize( mProcessedSize );
652  } else if (cacheOutput)
653  {
654  // collect data
655  if ( !outputBuffer.isOpen() ) {
656  outputBuffer.open(QIODevice::WriteOnly);
657  }
658  outputBuffer.seek( outputBufferIndex );
659  outputBuffer.write(buffer, buffer.size());
660  outputBufferIndex += buffer.size();
661  }
662 }
663 
664 void
665 IMAP4Protocol::parseRelay (ulong len)
666 {
667  if (relayEnabled)
668  totalSize (len);
669 }
670 
671 
672 bool IMAP4Protocol::parseRead(QByteArray & buffer, long len, long relay)
673 {
674  const long int bufLen = 8192;
675  char buf[bufLen];
676  // FIXME
677  while (buffer.size() < len )
678  {
679  ssize_t readLen = myRead(buf, qMin(len - buffer.size(), bufLen - 1));
680  if (readLen == 0)
681  {
682  kDebug(7116) <<"parseRead: readLen == 0 - connection broken";
683  error (ERR_CONNECTION_BROKEN, myHost);
684  setState(ISTATE_CONNECT);
685  closeConnection();
686  return false;
687  }
688  if (relay > buffer.size())
689  {
690  QByteArray relayData;
691  ssize_t relbuf = relay - buffer.size();
692  int currentRelay = qMin(relbuf, readLen);
693  relayData = QByteArray::fromRawData(buf, currentRelay);
694  parseRelay(relayData);
695  relayData.clear();
696  }
697  {
698  QBuffer stream( &buffer );
699  stream.open (QIODevice::WriteOnly);
700  stream.seek (buffer.size ());
701  stream.write (buf, readLen);
702  stream.close ();
703  }
704  }
705  return (buffer.size() == len);
706 }
707 
708 
709 bool IMAP4Protocol::parseReadLine (QByteArray & buffer, long relay)
710 {
711  if (myHost.isEmpty()) return false;
712 
713  while (true) {
714  ssize_t copyLen = 0;
715  if (readBufferLen > 0)
716  {
717  while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') copyLen++;
718  if (copyLen < readBufferLen) copyLen++;
719  if (relay > 0)
720  {
721  QByteArray relayData;
722 
723  if (copyLen < (ssize_t) relay)
724  relay = copyLen;
725  relayData = QByteArray::fromRawData (readBuffer, relay);
726  parseRelay (relayData);
727  relayData.clear();
728 // kDebug(7116) <<"relayed :" << relay <<"d";
729  }
730  // append to buffer
731  {
732  int oldsize = buffer.size();
733  buffer.resize(oldsize + copyLen);
734  memcpy(buffer.data() + oldsize, readBuffer, copyLen);
735 // kDebug(7116) <<"appended" << copyLen <<"d got now" << buffer.size();
736  }
737 
738  readBufferLen -= copyLen;
739  if (readBufferLen)
740  memmove(readBuffer, &readBuffer[copyLen], readBufferLen);
741  if (buffer[buffer.size() - 1] == '\n') return true;
742  }
743  if (!isConnected())
744  {
745  kDebug(7116) <<"parseReadLine - connection broken";
746  error (ERR_CONNECTION_BROKEN, myHost);
747  setState(ISTATE_CONNECT);
748  closeConnection();
749  return false;
750  }
751  if (!waitForResponse( responseTimeout() ))
752  {
753  error(ERR_SERVER_TIMEOUT, myHost);
754  setState(ISTATE_CONNECT);
755  closeConnection();
756  return false;
757  }
758  readBufferLen = read(readBuffer, IMAP_BUFFER - 1);
759  if (readBufferLen == 0)
760  {
761  kDebug(7116) <<"parseReadLine: readBufferLen == 0 - connection broken";
762  error (ERR_CONNECTION_BROKEN, myHost);
763  setState(ISTATE_CONNECT);
764  closeConnection();
765  return false;
766  }
767  }
768 }
769 
770 void
771 IMAP4Protocol::setSubURL (const KUrl & _url)
772 {
773  kDebug(7116) <<"IMAP4::setSubURL -" << _url.prettyUrl();
774  KIO::TCPSlaveBase::setSubUrl (_url);
775 }
776 
777 void
778 IMAP4Protocol::put (const KUrl & _url, int, KIO::JobFlags)
779 {
780  kDebug(7116) <<"IMAP4::put -" << _url.prettyUrl();
781 // KIO::TCPSlaveBase::put(_url,permissions,flags)
782  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
783  enum IMAP_TYPE aType =
784  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
785 
786  // see if it is a box
787  if (aType != ITYPE_BOX && aType != ITYPE_DIR_AND_BOX)
788  {
789  if (aBox[aBox.length () - 1] == '/')
790  aBox = aBox.right (aBox.length () - 1);
791  CommandPtr cmd = doCommand (imapCommand::clientCreate (aBox));
792 
793  if (cmd->result () != "OK") {
794  error (ERR_COULD_NOT_WRITE, _url.prettyUrl());
795  completeQueue.removeAll (cmd);
796  return;
797  }
798  completeQueue.removeAll (cmd);
799  }
800  else
801  {
802  QList < QByteArray* > bufferList;
803  int length = 0;
804 
805  int result;
806  // Loop until we got 'dataEnd'
807  do
808  {
809  QByteArray *buffer = new QByteArray ();
810  dataReq (); // Request for data
811  result = readData (*buffer);
812  if (result > 0)
813  {
814  bufferList.append (buffer);
815  length += result;
816  } else {
817  delete buffer;
818  }
819  }
820  while (result > 0);
821 
822  if (result != 0)
823  {
824  error (ERR_ABORTED, _url.prettyUrl());
825  return;
826  }
827 
828  CommandPtr cmd =
829  sendCommand (imapCommand::clientAppend (aBox, aSection, length));
830  while (!parseLoop ()) {}
831 
832  // see if server is waiting
833  if (!cmd->isComplete () && !getContinuation ().isEmpty ())
834  {
835  bool sendOk = true;
836  ulong wrote = 0;
837 
838  QByteArray *buffer;
839  QListIterator<QByteArray *> it(bufferList);
840  // send data to server
841  while (it.hasNext() && sendOk)
842  {
843  buffer = it.next();
844 
845  sendOk =
846  (write (buffer->data (), buffer->size ()) ==
847  (ssize_t) buffer->size ());
848  wrote += buffer->size ();
849  processedSize(wrote);
850  delete buffer;
851  if (!sendOk)
852  {
853  error (ERR_CONNECTION_BROKEN, myHost);
854  completeQueue.removeAll (cmd);
855  setState(ISTATE_CONNECT);
856  closeConnection();
857  return;
858  }
859  }
860  parseWriteLine ("");
861  // Wait until cmd is complete, or connection breaks.
862  while (!cmd->isComplete () && getState() != ISTATE_NO)
863  parseLoop ();
864  if ( getState() == ISTATE_NO ) {
865  // TODO KDE4: pass cmd->resultInfo() as third argument.
866  // ERR_CONNECTION_BROKEN expects a host, no way to pass details about the problem.
867  error( ERR_CONNECTION_BROKEN, myHost );
868  completeQueue.removeAll (cmd);
869  closeConnection();
870  return;
871  }
872  else if (cmd->result () != "OK") {
873  error( ERR_SLAVE_DEFINED, cmd->resultInfo() );
874  completeQueue.removeAll (cmd);
875  return;
876  }
877  else
878  {
879  if (hasCapability("UIDPLUS"))
880  {
881  QString uid = cmd->resultInfo();
882  if ( uid.contains("APPENDUID") )
883  {
884  uid = uid.section(' ', 2, 2);
885  uid.truncate(uid.length()-1);
886  infoMessage("UID "+uid);
887  }
888  }
889  // MUST reselect to get the new message
890  else if (aBox == getCurrentBox ())
891  {
892  cmd =
893  doCommand (imapCommand::
894  clientSelect (aBox, !selectInfo.readWrite ()));
895  completeQueue.removeAll (cmd);
896  }
897  }
898  }
899  else
900  {
901  //error (ERR_COULD_NOT_WRITE, myHost);
902  // Better ship the error message, e.g. "Over Quota"
903  error (ERR_SLAVE_DEFINED, cmd->resultInfo());
904  completeQueue.removeAll (cmd);
905  return;
906  }
907 
908  completeQueue.removeAll (cmd);
909  }
910 
911  finished ();
912 }
913 
914 void
915 IMAP4Protocol::mkdir (const KUrl & _url, int)
916 {
917  kDebug(7116) <<"IMAP4::mkdir -" << _url.prettyUrl();
918  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
919  parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
920  kDebug(7116) <<"IMAP4::mkdir - create" << aBox;
921  CommandPtr cmd = doCommand (imapCommand::clientCreate(aBox));
922 
923  if (cmd->result () != "OK")
924  {
925  kDebug(7116) <<"IMAP4::mkdir -" << cmd->resultInfo();
926  error (ERR_COULD_NOT_MKDIR, _url.prettyUrl());
927  completeQueue.removeAll (cmd);
928  return;
929  }
930  completeQueue.removeAll (cmd);
931 
932  // start a new listing to find the type of the folder
933  enum IMAP_TYPE type =
934  parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
935  if (type == ITYPE_BOX)
936  {
937  bool ask = ( aInfo.contains( "ASKUSER" ) );
938  if ( ask &&
939  messageBox(QuestionYesNo,
940  i18n("The following folder will be created on the server: %1 "
941  "What do you want to store in this folder?", aBox ),
942  i18n("Create Folder"),
943  i18n("&Messages"), i18n("&Subfolders")) == KMessageBox::No )
944  {
945  cmd = doCommand(imapCommand::clientDelete(aBox));
946  completeQueue.removeAll (cmd);
947  cmd = doCommand(imapCommand::clientCreate(aBox + aDelimiter));
948  if (cmd->result () != "OK")
949  {
950  error (ERR_COULD_NOT_MKDIR, _url.prettyUrl());
951  completeQueue.removeAll (cmd);
952  return;
953  }
954  completeQueue.removeAll (cmd);
955  }
956  }
957 
958  cmd = doCommand(imapCommand::clientSubscribe(aBox));
959  completeQueue.removeAll(cmd);
960 
961  finished ();
962 }
963 
964 void
965 IMAP4Protocol::copy (const KUrl & src, const KUrl & dest, int, KIO::JobFlags flags)
966 {
967  kDebug(7116) <<"IMAP4::copy - [" << ((flags & KIO::Overwrite) ?"Overwrite" :"NoOverwrite") <<"]" << src.prettyUrl() <<" ->" << dest.prettyUrl();
968  QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
969  QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo;
970  enum IMAP_TYPE sType =
971  parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo);
972  enum IMAP_TYPE dType =
973  parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo);
974 
975  // see if we have to create anything
976  if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX)
977  {
978  // this might be konqueror
979  int sub = dBox.indexOf (sBox);
980 
981  // might be moving to upper folder
982  if (sub > 0)
983  {
984  KUrl testDir = dest;
985 
986  QString subDir = dBox.right (dBox.length () - dBox.lastIndexOf ('/'));
987  QString topDir = dBox.left (sub);
988  testDir.setPath ('/' + topDir);
989  dType =
990  parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity,
991  dDelimiter, dInfo);
992 
993  kDebug(7116) <<"IMAP4::copy - checking this destination" << topDir;
994  // see if this is what the user wants
995  if (dType == ITYPE_BOX || dType == ITYPE_DIR_AND_BOX)
996  {
997  kDebug(7116) <<"IMAP4::copy - assuming this destination" << topDir;
998  dBox = topDir;
999  }
1000  else
1001  {
1002 
1003  // maybe if we create a new mailbox
1004  topDir = '/' + topDir + subDir;
1005  testDir.setPath (topDir);
1006  kDebug(7116) <<"IMAP4::copy - checking this destination" << topDir;
1007  dType =
1008  parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity,
1009  dDelimiter, dInfo);
1010  if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX)
1011  {
1012  // ok then we'll create a mailbox
1013  CommandPtr cmd = doCommand (imapCommand::clientCreate (topDir));
1014 
1015  // on success we'll use it, else we'll just try to create the given dir
1016  if (cmd->result () == "OK")
1017  {
1018  kDebug(7116) <<"IMAP4::copy - assuming this destination" << topDir;
1019  dType = ITYPE_BOX;
1020  dBox = topDir;
1021  }
1022  else
1023  {
1024  completeQueue.removeAll (cmd);
1025  cmd = doCommand (imapCommand::clientCreate (dBox));
1026  if (cmd->result () == "OK")
1027  dType = ITYPE_BOX;
1028  else
1029  error (ERR_COULD_NOT_WRITE, dest.prettyUrl());
1030  }
1031  completeQueue.removeAll (cmd);
1032  }
1033  }
1034 
1035  }
1036  }
1037  if (sType == ITYPE_MSG || sType == ITYPE_BOX || sType == ITYPE_DIR_AND_BOX)
1038  {
1039  //select the source box
1040  if (!assureBox(sBox, true)) return;
1041  kDebug(7116) <<"IMAP4::copy -" << sBox <<" ->" << dBox;
1042 
1043  //issue copy command
1044  CommandPtr cmd =
1045  doCommand (imapCommand::clientCopy (dBox, sSequence));
1046  if (cmd->result () != "OK")
1047  {
1048  kError(5006) <<"IMAP4::copy -" << cmd->resultInfo();
1049  error (ERR_COULD_NOT_WRITE, dest.prettyUrl());
1050  completeQueue.removeAll (cmd);
1051  return;
1052  } else {
1053  if (hasCapability("UIDPLUS"))
1054  {
1055  QString uid = cmd->resultInfo();
1056  if ( uid.contains("COPYUID") )
1057  {
1058  uid = uid.section(' ', 2, 3);
1059  uid.truncate(uid.length()-1);
1060  infoMessage("UID "+uid);
1061  }
1062  }
1063  }
1064  completeQueue.removeAll (cmd);
1065  }
1066  else
1067  {
1068  error (ERR_ACCESS_DENIED, src.prettyUrl());
1069  return;
1070  }
1071  finished ();
1072 }
1073 
1074 void
1075 IMAP4Protocol::del (const KUrl & _url, bool isFile)
1076 {
1077  kDebug(7116) <<"IMAP4::del - [" << (isFile ?"File" :"NoFile") <<"]" << _url.prettyUrl();
1078  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1079  enum IMAP_TYPE aType =
1080  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1081 
1082  switch (aType)
1083  {
1084  case ITYPE_BOX:
1085  case ITYPE_DIR_AND_BOX:
1086  if (!aSequence.isEmpty ())
1087  {
1088  if (aSequence == "*")
1089  {
1090  if (!assureBox (aBox, false)) return;
1091  CommandPtr cmd = doCommand (imapCommand::clientExpunge ());
1092  if (cmd->result () != "OK") {
1093  error (ERR_CANNOT_DELETE, _url.prettyUrl());
1094  completeQueue.removeAll (cmd);
1095  return;
1096  }
1097  completeQueue.removeAll (cmd);
1098  }
1099  else
1100  {
1101  // if open for read/write
1102  if (!assureBox (aBox, false)) return;
1103  CommandPtr cmd =
1104  doCommand (imapCommand::
1105  clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED"));
1106  if (cmd->result () != "OK") {
1107  error (ERR_CANNOT_DELETE, _url.prettyUrl());
1108  completeQueue.removeAll (cmd);
1109  return;
1110  }
1111  completeQueue.removeAll (cmd);
1112  }
1113  }
1114  else
1115  {
1116  if (getCurrentBox() == aBox)
1117  {
1118  CommandPtr cmd = doCommand(imapCommand::clientClose());
1119  completeQueue.removeAll(cmd);
1120  setState(ISTATE_LOGIN);
1121  }
1122  // We unsubscribe, otherwise we get ghost folders on UW-IMAP
1123  CommandPtr cmd = doCommand(imapCommand::clientUnsubscribe(aBox));
1124  completeQueue.removeAll(cmd);
1125  cmd = doCommand(imapCommand::clientDelete (aBox));
1126  // If this doesn't work, we try to empty the mailbox first
1127  if (cmd->result () != "OK")
1128  {
1129  completeQueue.removeAll(cmd);
1130  if (!assureBox(aBox, false)) return;
1131  bool stillOk = true;
1132  if (stillOk)
1133  {
1134  CommandPtr cmd = doCommand(
1135  imapCommand::clientStore("1:*", "+FLAGS.SILENT", "\\DELETED"));
1136  if (cmd->result () != "OK") stillOk = false;
1137  completeQueue.removeAll(cmd);
1138  }
1139  if (stillOk)
1140  {
1141  CommandPtr cmd = doCommand(imapCommand::clientClose());
1142  if (cmd->result () != "OK") stillOk = false;
1143  completeQueue.removeAll(cmd);
1144  setState(ISTATE_LOGIN);
1145  }
1146  if (stillOk)
1147  {
1148  CommandPtr cmd = doCommand (imapCommand::clientDelete(aBox));
1149  if (cmd->result () != "OK") stillOk = false;
1150  completeQueue.removeAll(cmd);
1151  }
1152  if (!stillOk)
1153  {
1154  error (ERR_COULD_NOT_RMDIR, _url.prettyUrl());
1155  return;
1156  }
1157  } else {
1158  completeQueue.removeAll (cmd);
1159  }
1160  }
1161  break;
1162 
1163  case ITYPE_DIR:
1164  {
1165  CommandPtr cmd = doCommand (imapCommand::clientDelete (aBox));
1166  if (cmd->result () != "OK") {
1167  error (ERR_COULD_NOT_RMDIR, _url.prettyUrl());
1168  completeQueue.removeAll (cmd);
1169  return;
1170  }
1171  completeQueue.removeAll (cmd);
1172  }
1173  break;
1174 
1175  case ITYPE_MSG:
1176  {
1177  // if open for read/write
1178  if (!assureBox (aBox, false)) return;
1179  CommandPtr cmd =
1180  doCommand (imapCommand::
1181  clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED"));
1182  if (cmd->result () != "OK") {
1183  error (ERR_CANNOT_DELETE, _url.prettyUrl());
1184  completeQueue.removeAll (cmd);
1185  return;
1186  }
1187  completeQueue.removeAll (cmd);
1188  }
1189  break;
1190 
1191  case ITYPE_UNKNOWN:
1192  case ITYPE_ATTACH:
1193  error (ERR_CANNOT_DELETE, _url.prettyUrl());
1194  break;
1195  }
1196  finished ();
1197 }
1198 
1199 /*
1200  * Copy a mail: data = 'C' + srcURL (KUrl) + destURL (KUrl)
1201  * Capabilities: data = 'c'. Result shipped in infoMessage() signal
1202  * No-op: data = 'N'
1203  * Namespace: data = 'n'. Result shipped in infoMessage() signal
1204  * The format is: section=namespace=delimiter
1205  * Note that the namespace can be empty
1206  * Unsubscribe: data = 'U' + URL (KUrl)
1207  * Subscribe: data = 'u' + URL (KUrl)
1208  * Change the status: data = 'S' + URL (KUrl) + Flags (QCString)
1209  * ACL commands: data = 'A' + command + URL (KUrl) + command-dependent args
1210  * AnnotateMore commands: data = 'M' + 'G'et/'S'et + URL + entry + command-dependent args
1211  * Search: data = 'E' + URL (KUrl)
1212  * Quota commands: data = 'Q' + 'R'oot/'G'et/'S'et + URL + entry + command-dependent args
1213  * Custom command: data = 'X' + 'N'ormal/'E'xtended + command + command-dependent args
1214  */
1215 void
1216 IMAP4Protocol::special (const QByteArray & aData)
1217 {
1218  kDebug(7116) <<"IMAP4Protocol::special";
1219  if (!makeLogin()) return;
1220 
1221  QDataStream stream( aData );
1222 
1223  int tmp;
1224  stream >> tmp;
1225 
1226  switch (tmp) {
1227  case 'C':
1228  {
1229  // copy
1230  KUrl src;
1231  KUrl dest;
1232  stream >> src >> dest;
1233  copy(src, dest, 0, KIO::DefaultFlags);
1234  break;
1235  }
1236  case 'c':
1237  {
1238  // capabilities
1239  infoMessage(imapCapabilities.join(" "));
1240  finished();
1241  break;
1242  }
1243  case 'N':
1244  {
1245  // NOOP
1246  CommandPtr cmd = doCommand(imapCommand::clientNoop());
1247  if (cmd->result () != "OK")
1248  {
1249  kDebug(7116) <<"NOOP did not succeed - connection broken";
1250  completeQueue.removeAll (cmd);
1251  error (ERR_CONNECTION_BROKEN, myHost);
1252  return;
1253  }
1254  completeQueue.removeAll (cmd);
1255  finished();
1256  break;
1257  }
1258  case 'n':
1259  {
1260  // namespace in the form "section=namespace=delimiter"
1261  // entries are separated by ,
1262  infoMessage( imapNamespaces.join(",") );
1263  finished();
1264  break;
1265  }
1266  case 'U':
1267  {
1268  // unsubscribe
1269  KUrl _url;
1270  stream >> _url;
1271  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1272  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1273  CommandPtr cmd = doCommand(imapCommand::clientUnsubscribe(aBox));
1274  if (cmd->result () != "OK")
1275  {
1276  completeQueue.removeAll (cmd);
1277  error(ERR_SLAVE_DEFINED, i18n("Unsubscribe of folder %1 "
1278  "failed. The server returned: %2",
1279  _url.prettyUrl(),
1280  cmd->resultInfo()));
1281  return;
1282  }
1283  completeQueue.removeAll (cmd);
1284  finished();
1285  break;
1286  }
1287  case 'u':
1288  {
1289  // subscribe
1290  KUrl _url;
1291  stream >> _url;
1292  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1293  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1294  CommandPtr cmd = doCommand(imapCommand::clientSubscribe(aBox));
1295  if (cmd->result () != "OK")
1296  {
1297  completeQueue.removeAll (cmd);
1298  error(ERR_SLAVE_DEFINED, i18n("Subscribe of folder %1 "
1299  "failed. The server returned: %2",
1300  _url.prettyUrl(),
1301  cmd->resultInfo()));
1302  return;
1303  }
1304  completeQueue.removeAll (cmd);
1305  finished();
1306  break;
1307  }
1308  case 'A':
1309  {
1310  // acl
1311  int cmd;
1312  stream >> cmd;
1313  if ( hasCapability( "ACL" ) ) {
1314  specialACLCommand( cmd, stream );
1315  } else {
1316  error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("ACL") );
1317  }
1318  break;
1319  }
1320  case 'M':
1321  {
1322  // annotatemore
1323  int cmd;
1324  stream >> cmd;
1325  if ( hasCapability( "ANNOTATEMORE" ) ) {
1326  specialAnnotateMoreCommand( cmd, stream );
1327  } else {
1328  error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("ANNOTATEMORE") );
1329  }
1330  break;
1331  }
1332  case 'Q':
1333  {
1334  // quota
1335  int cmd;
1336  stream >> cmd;
1337  if ( hasCapability( "QUOTA" ) ) {
1338  specialQuotaCommand( cmd, stream );
1339  } else {
1340  error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("QUOTA") );
1341  }
1342  break;
1343  }
1344  case 'S':
1345  {
1346  // status
1347  KUrl _url;
1348  QByteArray newFlags;
1349  stream >> _url >> newFlags;
1350 
1351  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1352  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1353  if (!assureBox(aBox, false)) return;
1354 
1355  // make sure we only touch flags we know
1356  QByteArray knownFlags = "\\SEEN \\ANSWERED \\FLAGGED \\DRAFT";
1357  const imapInfo info = getSelected();
1358  if ( info.permanentFlagsAvailable() && (info.permanentFlags() & imapInfo::User) ) {
1359  knownFlags += " KMAILFORWARDED KMAILTODO KMAILWATCHED KMAILIGNORED $FORWARDED $TODO $WATCHED $IGNORED";
1360  }
1361 
1362  CommandPtr cmd = doCommand (imapCommand::
1363  clientStore (aSequence, "-FLAGS.SILENT", knownFlags));
1364  if (cmd->result () != "OK")
1365  {
1366  completeQueue.removeAll (cmd);
1367  error(ERR_SLAVE_DEFINED, i18n("Changing the flags of message %1 "
1368  "failed with %2.", _url.prettyUrl(), cmd->result()));
1369  return;
1370  }
1371  completeQueue.removeAll (cmd);
1372  if (!newFlags.isEmpty())
1373  {
1374  cmd = doCommand (imapCommand::
1375  clientStore (aSequence, "+FLAGS.SILENT", newFlags));
1376  if (cmd->result () != "OK")
1377  {
1378  completeQueue.removeAll (cmd);
1379  error(ERR_SLAVE_DEFINED, i18n("Silent Changing the flags of message %1 "
1380  "failed with %2.", _url.prettyUrl(), cmd->result()));
1381  return;
1382  }
1383  completeQueue.removeAll (cmd);
1384  }
1385  finished();
1386  break;
1387  }
1388  case 's':
1389  {
1390  // seen
1391  KUrl _url;
1392  bool seen;
1393  QByteArray newFlags;
1394  stream >> _url >> seen;
1395 
1396  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1397  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1398  if ( !assureBox(aBox, true) ) // read-only because changing SEEN should be possible even then
1399  return;
1400 
1401  CommandPtr cmd;
1402  if ( seen )
1403  cmd = doCommand( imapCommand::clientStore( aSequence, "+FLAGS.SILENT", "\\SEEN" ) );
1404  else
1405  cmd = doCommand( imapCommand::clientStore( aSequence, "-FLAGS.SILENT", "\\SEEN" ) );
1406 
1407  if (cmd->result () != "OK")
1408  {
1409  completeQueue.removeAll (cmd);
1410  error(ERR_SLAVE_DEFINED,
1411  i18n( "Changing the flags of message %1 failed.", _url.prettyUrl() ) );
1412  return;
1413  }
1414  completeQueue.removeAll (cmd);
1415  finished();
1416  break;
1417  }
1418 
1419  case 'E':
1420  {
1421  // search
1422  specialSearchCommand( stream );
1423  break;
1424  }
1425  case 'X':
1426  {
1427  // custom command
1428  specialCustomCommand( stream );
1429  break;
1430  }
1431  default:
1432  kWarning(7116) <<"Unknown command in special():" << tmp;
1433  error( ERR_UNSUPPORTED_ACTION, QString(QChar(tmp)) );
1434  break;
1435  }
1436 }
1437 
1438 void
1439 IMAP4Protocol::specialACLCommand( int command, QDataStream& stream )
1440 {
1441  // All commands start with the URL to the box
1442  KUrl _url;
1443  stream >> _url;
1444  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1445  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1446 
1447  switch( command ) {
1448  case 'S': // SETACL
1449  {
1450  QString user, acl;
1451  stream >> user >> acl;
1452  kDebug(7116) <<"SETACL" << aBox << user << acl;
1453  CommandPtr cmd = doCommand(imapCommand::clientSetACL(aBox, user, acl));
1454  if (cmd->result () != "OK")
1455  {
1456  error(ERR_SLAVE_DEFINED, i18n("Setting the Access Control List on folder %1 "
1457  "for user %2 failed. The server returned: %3",
1458  _url.prettyUrl(),
1459  user,
1460  cmd->resultInfo()));
1461  return;
1462  }
1463  completeQueue.removeAll (cmd);
1464  finished();
1465  break;
1466  }
1467  case 'D': // DELETEACL
1468  {
1469  QString user;
1470  stream >> user;
1471  kDebug(7116) <<"DELETEACL" << aBox << user;
1472  CommandPtr cmd = doCommand(imapCommand::clientDeleteACL(aBox, user));
1473  if (cmd->result () != "OK")
1474  {
1475  error(ERR_SLAVE_DEFINED, i18n("Deleting the Access Control List on folder %1 "
1476  "for user %2 failed. The server returned: %3",
1477  _url.prettyUrl(),
1478  user,
1479  cmd->resultInfo()));
1480  return;
1481  }
1482  completeQueue.removeAll (cmd);
1483  finished();
1484  break;
1485  }
1486  case 'G': // GETACL
1487  {
1488  kDebug(7116) <<"GETACL" << aBox;
1489  CommandPtr cmd = doCommand(imapCommand::clientGetACL(aBox));
1490  if (cmd->result () != "OK")
1491  {
1492  error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 "
1493  "failed. The server returned: %2",
1494  _url.prettyUrl(),
1495  cmd->resultInfo()));
1496  return;
1497  }
1498  // Returning information to the application from a special() command isn't easy.
1499  // I'm reusing the infoMessage trick seen above (for capabilities), but this
1500  // limits me to a string instead of a stringlist. Using DQUOTE as separator,
1501  // because it's forbidden in userids by rfc3501
1502  kDebug(7116) << getResults();
1503  infoMessage(getResults().join( "\"" ));
1504  finished();
1505  break;
1506  }
1507  case 'L': // LISTRIGHTS
1508  {
1509  // Do we need this one? It basically shows which rights are tied together, but that's all?
1510  error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
1511  break;
1512  }
1513  case 'M': // MYRIGHTS
1514  {
1515  kDebug(7116) <<"MYRIGHTS" << aBox;
1516  CommandPtr cmd = doCommand(imapCommand::clientMyRights(aBox));
1517  if (cmd->result () != "OK")
1518  {
1519  error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 "
1520  "failed. The server returned: %2",
1521  _url.prettyUrl(),
1522  cmd->resultInfo()));
1523  return;
1524  }
1525  QStringList lst = getResults();
1526  kDebug(7116) <<"myrights results:" << lst;
1527  if ( !lst.isEmpty() ) {
1528  Q_ASSERT( lst.count() == 1 );
1529  infoMessage( lst.first() );
1530  }
1531  finished();
1532  break;
1533  }
1534  default:
1535  kWarning(7116) <<"Unknown special ACL command:" << command;
1536  error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
1537  }
1538 }
1539 
1540 void
1541 IMAP4Protocol::specialSearchCommand( QDataStream& stream )
1542 {
1543  kDebug(7116) <<"IMAP4Protocol::specialSearchCommand";
1544  KUrl _url;
1545  stream >> _url;
1546  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1547  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1548  if (!assureBox(aBox, true)) return;
1549 
1550  CommandPtr cmd = doCommand (imapCommand::clientSearch( aSection ));
1551  if (cmd->result () != "OK")
1552  {
1553  error(ERR_SLAVE_DEFINED, i18n("Searching of folder %1 "
1554  "failed. The server returned: %2",
1555  aBox,
1556  cmd->resultInfo()));
1557  return;
1558  }
1559  completeQueue.removeAll(cmd);
1560  QStringList lst = getResults();
1561  kDebug(7116) <<"IMAP4Protocol::specialSearchCommand '" << aSection <<
1562  "' returns" << lst;
1563  infoMessage( lst.join( " " ) );
1564 
1565  finished();
1566 }
1567 
1568 void
1569 IMAP4Protocol::specialCustomCommand( QDataStream& stream )
1570 {
1571  kDebug(7116) << "IMAP4Protocol::specialCustomCommand" << endl;
1572 
1573  QString command, arguments;
1574  int type;
1575  stream >> type;
1576  stream >> command >> arguments;
1577 
1582  if ( type == 'N' ) {
1583  kDebug(7116) << "IMAP4Protocol::specialCustomCommand: normal mode" << endl;
1584  CommandPtr cmd = doCommand (imapCommand::clientCustom( command, arguments ));
1585  if (cmd->result () != "OK")
1586  {
1587  error( ERR_SLAVE_DEFINED,
1588  i18n( "Custom command %1:%2 failed. The server returned: %3",
1589  command, arguments, cmd->resultInfo() ) );
1590  return;
1591  }
1592  completeQueue.removeAll(cmd);
1593  QStringList lst = getResults();
1594  kDebug(7116) << "IMAP4Protocol::specialCustomCommand '" << command <<
1595  ":" << arguments <<
1596  "' returns " << lst << endl;
1597  infoMessage( lst.join( " " ) );
1598 
1599  finished();
1600  } else
1605  if ( type == 'E' ) {
1606  kDebug(7116) << "IMAP4Protocol::specialCustomCommand: extended mode" << endl;
1607  CommandPtr cmd = sendCommand (imapCommand::clientCustom( command, QString() ));
1608  while ( !parseLoop () ) {};
1609 
1610  // see if server is waiting
1611  if (!cmd->isComplete () && !getContinuation ().isEmpty ())
1612  {
1613  const QByteArray buffer = arguments.toUtf8();
1614 
1615  // send data to server
1616  bool sendOk = (write (buffer.data (), buffer.size ()) == (ssize_t)buffer.size ());
1617  processedSize( buffer.size() );
1618 
1619  if ( !sendOk ) {
1620  error ( ERR_CONNECTION_BROKEN, myHost );
1621  completeQueue.removeAll ( cmd );
1622  setState(ISTATE_CONNECT);
1623  closeConnection();
1624  return;
1625  }
1626  }
1627  parseWriteLine ("");
1628 
1629  do
1630  {
1631  while (!parseLoop ()) {};
1632  }
1633  while (!cmd->isComplete ());
1634 
1635  completeQueue.removeAll (cmd);
1636 
1637  QStringList lst = getResults();
1638  kDebug(7116) << "IMAP4Protocol::specialCustomCommand: returns " << lst << endl;
1639  infoMessage( lst.join( " " ) );
1640 
1641  finished ();
1642  }
1643 }
1644 
1645 void
1646 IMAP4Protocol::specialAnnotateMoreCommand( int command, QDataStream& stream )
1647 {
1648  // All commands start with the URL to the box
1649  KUrl _url;
1650  stream >> _url;
1651  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1652  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1653 
1654  switch( command ) {
1655  case 'S': // SETANNOTATION
1656  {
1657  // Params:
1658  // KUrl URL of the mailbox
1659  // QString entry (should be an actual entry name, no % or *; empty for server entries)
1660  // QMap<QString,QString> attributes (name and value)
1661  QString entry;
1662  QMap<QString, QString> attributes;
1663  stream >> entry >> attributes;
1664  kDebug(7116) <<"SETANNOTATION" << aBox << entry << attributes.count() <<" attributes";
1665  CommandPtr cmd = doCommand(imapCommand::clientSetAnnotation(aBox, entry, attributes));
1666  if (cmd->result () != "OK")
1667  {
1668  error(ERR_SLAVE_DEFINED, i18n("Setting the annotation %1 on folder %2 "
1669  " failed. The server returned: %3",
1670  entry,
1671  _url.prettyUrl(),
1672  cmd->resultInfo()));
1673  return;
1674  }
1675  completeQueue.removeAll (cmd);
1676  finished();
1677  break;
1678  }
1679  case 'G': // GETANNOTATION.
1680  {
1681  // Params:
1682  // KUrl URL of the mailbox
1683  // QString entry (should be an actual entry name, no % or *; empty for server entries)
1684  // QStringList attributes (list of attributes to be retrieved, possibly with % or *)
1685  QString entry;
1686  QStringList attributeNames;
1687  stream >> entry >> attributeNames;
1688  kDebug(7116) <<"GETANNOTATION" << aBox << entry << attributeNames;
1689  CommandPtr cmd = doCommand(imapCommand::clientGetAnnotation(aBox, entry, attributeNames));
1690  if (cmd->result () != "OK")
1691  {
1692  error(ERR_SLAVE_DEFINED, i18n("Retrieving the annotation %1 on folder %2 "
1693  "failed. The server returned: %3",
1694  entry,
1695  _url.prettyUrl(),
1696  cmd->resultInfo()));
1697  return;
1698  }
1699  // Returning information to the application from a special() command isn't easy.
1700  // I'm reusing the infoMessage trick seen above (for capabilities and acls), but this
1701  // limits me to a string instead of a stringlist. Let's use \r as separator.
1702  kDebug(7116) << getResults();
1703  infoMessage(getResults().join( "\r" ));
1704  finished();
1705  break;
1706  }
1707  default:
1708  kWarning(7116) <<"Unknown special annotate command:" << command;
1709  error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
1710  }
1711 }
1712 
1713 void
1714 IMAP4Protocol::specialQuotaCommand( int command, QDataStream& stream )
1715 {
1716  // All commands start with the URL to the box
1717  KUrl _url;
1718  stream >> _url;
1719  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1720  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo);
1721 
1722  switch( command ) {
1723  case 'R': // GETQUOTAROOT
1724  {
1725  kDebug(7116) <<"QUOTAROOT" << aBox;
1726  CommandPtr cmd = doCommand(imapCommand::clientGetQuotaroot( aBox ) );
1727  if (cmd->result () != "OK")
1728  {
1729  error(ERR_SLAVE_DEFINED, i18n("Retrieving the quota root information on folder %1 "
1730  "failed. The server returned: %2",
1731  _url.prettyUrl(), cmd->resultInfo()));
1732  return;
1733  }
1734  infoMessage(getResults().join( "\r" ));
1735  finished();
1736  break;
1737  }
1738  case 'G': // GETQUOTA
1739  {
1740  kDebug(7116) <<"GETQUOTA command";
1741  kWarning(7116) <<"UNIMPLEMENTED";
1742  break;
1743  }
1744  case 'S': // SETQUOTA
1745  {
1746  kDebug(7116) <<"SETQUOTA command";
1747  kWarning(7116) <<"UNIMPLEMENTED";
1748  break;
1749  }
1750  default:
1751  kWarning(7116) <<"Unknown special quota command:" << command;
1752  error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) );
1753  }
1754 }
1755 
1756 
1757 void
1758 IMAP4Protocol::rename (const KUrl & src, const KUrl & dest, KIO::JobFlags flags)
1759 {
1760  kDebug(7116) <<"IMAP4::rename - [" << ((flags & KIO::Overwrite) ?"Overwrite" :"NoOverwrite") <<"]" << src <<" ->" << dest;
1761  QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo;
1762  QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo;
1763  enum IMAP_TYPE sType =
1764  parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo, false);
1765  enum IMAP_TYPE dType =
1766  parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo, false);
1767 
1768  if (dType == ITYPE_UNKNOWN)
1769  {
1770  switch (sType)
1771  {
1772  case ITYPE_BOX:
1773  case ITYPE_DIR:
1774  case ITYPE_DIR_AND_BOX:
1775  {
1776  if (getState() == ISTATE_SELECT && sBox == getCurrentBox())
1777  {
1778  kDebug(7116) <<"IMAP4::rename - close" << getCurrentBox();
1779  // mailbox can only be renamed if it is closed
1780  CommandPtr cmd = doCommand (imapCommand::clientClose());
1781  bool ok = cmd->result() == "OK";
1782  completeQueue.removeAll(cmd);
1783  if (!ok)
1784  {
1785  error(ERR_SLAVE_DEFINED, i18n("Unable to close mailbox."));
1786  return;
1787  }
1788  setState(ISTATE_LOGIN);
1789  }
1790  CommandPtr cmd = doCommand (imapCommand::clientRename (sBox, dBox));
1791  if (cmd->result () != "OK") {
1792  error (ERR_CANNOT_RENAME, cmd->result ());
1793  completeQueue.removeAll (cmd);
1794  return;
1795  }
1796  completeQueue.removeAll (cmd);
1797  }
1798  break;
1799 
1800  case ITYPE_MSG:
1801  case ITYPE_ATTACH:
1802  case ITYPE_UNKNOWN:
1803  error (ERR_CANNOT_RENAME, src.prettyUrl());
1804  break;
1805  }
1806  }
1807  else
1808  {
1809  error (ERR_CANNOT_RENAME, src.prettyUrl());
1810  return;
1811  }
1812  finished ();
1813 }
1814 
1815 void
1816 IMAP4Protocol::slave_status ()
1817 {
1818  bool connected = (getState() != ISTATE_NO) && isConnected();
1819  kDebug(7116) <<"IMAP4::slave_status" << connected;
1820  slaveStatus ( connected ? myHost : QString(), connected );
1821 }
1822 
1823 void
1824 IMAP4Protocol::dispatch (int command, const QByteArray & data)
1825 {
1826  kDebug(7116) <<"IMAP4::dispatch - command=" << command;
1827  KIO::TCPSlaveBase::dispatch (command, data);
1828 }
1829 
1830 void
1831 IMAP4Protocol::stat (const KUrl & _url)
1832 {
1833  kDebug(7116) <<"IMAP4::stat -" << _url.prettyUrl();
1834  QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo;
1835  // parseURL with caching
1836  enum IMAP_TYPE aType =
1837  parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter,
1838  aInfo, true);
1839 
1840  UDSEntry entry;
1841 
1842  entry.insert( UDSEntry::UDS_NAME, aBox);
1843 
1844  if (!aSection.isEmpty())
1845  {
1846  if (getState() == ISTATE_SELECT && aBox == getCurrentBox())
1847  {
1848  CommandPtr cmd = doCommand (imapCommand::clientClose());
1849  bool ok = cmd->result() == "OK";
1850  completeQueue.removeAll(cmd);
1851  if (!ok)
1852  {
1853  error(ERR_SLAVE_DEFINED, i18n("Unable to close mailbox."));
1854  return;
1855  }
1856  setState(ISTATE_LOGIN);
1857  }
1858  bool ok = false;
1859  QString cmdInfo;
1860  if (aType == ITYPE_MSG || aType == ITYPE_ATTACH)
1861  ok = true;
1862  else
1863  {
1864  CommandPtr cmd = doCommand(imapCommand::clientStatus(aBox, aSection));
1865  ok = cmd->result() == "OK";
1866  cmdInfo = cmd->resultInfo();
1867  completeQueue.removeAll(cmd);
1868  }
1869  if (!ok)
1870  {
1871  bool found = false;
1872  CommandPtr cmd = doCommand (imapCommand::clientList ("", aBox));
1873  if (cmd->result () == "OK")
1874  {
1875  for (QList< imapList >::Iterator it = listResponses.begin ();
1876  it != listResponses.end (); ++it)
1877  {
1878  if (aBox == (*it).name ()) found = true;
1879  }
1880  }
1881  completeQueue.removeAll (cmd);
1882  if (found)
1883  error(ERR_SLAVE_DEFINED, i18n("Unable to get information about folder %1. The server replied: %2", aBox, cmdInfo));
1884  else
1885  error(KIO::ERR_DOES_NOT_EXIST, aBox);
1886  return;
1887  }
1888  if ((aSection == "UIDNEXT" && getStatus().uidNextAvailable())
1889  || (aSection == "UNSEEN" && getStatus().unseenAvailable()))
1890  {
1891  entry.insert( UDSEntry::UDS_SIZE, (aSection == "UIDNEXT") ? getStatus().uidNext()
1892  : getStatus().unseen());
1893  }
1894  } else
1895  if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX || aType == ITYPE_MSG ||
1896  aType == ITYPE_ATTACH)
1897  {
1898  ulong validity = 0;
1899  // see if the box is already in select/examine state
1900  if (aBox == getCurrentBox ())
1901  validity = selectInfo.uidValidity ();
1902  else
1903  {
1904  // do a status lookup on the box
1905  // only do this if the box is not selected
1906  // the server might change the validity for new select/examine
1907  CommandPtr cmd =
1908  doCommand (imapCommand::clientStatus (aBox, "UIDVALIDITY"));
1909  completeQueue.removeAll (cmd);
1910  validity = getStatus ().uidValidity ();
1911  }
1912 #ifdef __GNUC__
1913 #warning This is temporary since Dec 2000 and makes most of the below code invalid
1914 #endif
1915  validity = 0; // temporary
1916 
1917  if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX)
1918  {
1919  // has no or an invalid uidvalidity
1920  if (validity > 0 && validity != aValidity.toULong ())
1921  {
1922  //redirect
1923  KUrl newUrl = _url;
1924 
1925  newUrl.setPath ('/' + aBox + ";UIDVALIDITY=" +
1926  QString::number(validity));
1927  kDebug(7116) <<"IMAP4::stat - redirecting to" << newUrl.prettyUrl();
1928  redirection (newUrl);
1929  }
1930  }
1931  else if (aType == ITYPE_MSG || aType == ITYPE_ATTACH)
1932  {
1933  //must determine if this message exists
1934  //cause konqueror will check this on paste operations
1935 
1936  // has an invalid uidvalidity
1937  // or no messages in box
1938  if (validity > 0 && validity != aValidity.toULong ())
1939  {
1940  aType = ITYPE_UNKNOWN;
1941  kDebug(7116) <<"IMAP4::stat - url has invalid validity [" << validity <<"d]" << _url.prettyUrl();
1942  }
1943  }
1944  }
1945 
1946  entry.insert( UDSEntry::UDS_MIME_TYPE,getMimeType (aType));
1947 
1948  //kDebug(7116) <<"IMAP4: stat:" << atom.m_str;
1949  switch (aType)
1950  {
1951  case ITYPE_DIR:
1952  entry.insert( UDSEntry::UDS_FILE_TYPE, S_IFDIR);
1953  break;
1954 
1955  case ITYPE_BOX:
1956  case ITYPE_DIR_AND_BOX:
1957  entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR);
1958  break;
1959 
1960  case ITYPE_MSG:
1961  case ITYPE_ATTACH:
1962  entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFREG);
1963  break;
1964 
1965  case ITYPE_UNKNOWN:
1966  error (ERR_DOES_NOT_EXIST, _url.prettyUrl());
1967  break;
1968  }
1969 
1970  statEntry (entry);
1971  kDebug(7116) <<"IMAP4::stat - Finishing stat";
1972  finished ();
1973 }
1974 
1975 void IMAP4Protocol::openConnection()
1976 {
1977  if (makeLogin()) connected();
1978 }
1979 
1980 void IMAP4Protocol::closeConnection()
1981 {
1982  if (getState() == ISTATE_NO) return;
1983  if (getState() == ISTATE_SELECT && metaData("expunge") == "auto")
1984  {
1985  CommandPtr cmd = doCommand (imapCommand::clientExpunge());
1986  completeQueue.removeAll (cmd);
1987  }
1988  if (getState() != ISTATE_CONNECT)
1989  {
1990  CommandPtr cmd = doCommand (imapCommand::clientLogout());
1991  completeQueue.removeAll (cmd);
1992  }
1993  disconnectFromHost();
1994  setState(ISTATE_NO);
1995  completeQueue.clear();
1996  sentQueue.clear();
1997  lastHandled = 0;
1998  currentBox.clear();
1999  readBufferLen = 0;
2000 }
2001 
2002 bool IMAP4Protocol::makeLogin ()
2003 {
2004  if (getState () == ISTATE_LOGIN || getState () == ISTATE_SELECT)
2005  return true;
2006 
2007  kDebug(7116) <<"IMAP4::makeLogin - checking login";
2008  bool alreadyConnected = getState() == ISTATE_CONNECT;
2009  kDebug(7116) <<"IMAP4::makeLogin - alreadyConnected" << alreadyConnected;
2010  if (alreadyConnected || connectToHost (( mySSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL ), myHost,
2011  myPort))
2012  {
2013 // fcntl (m_iSock, F_SETFL, (fcntl (m_iSock, F_GETFL) | O_NDELAY));
2014 
2015  setState(ISTATE_CONNECT);
2016 
2017  myAuth = metaData("auth");
2018  myTLS = metaData("tls");
2019  kDebug(7116) <<"myAuth:" << myAuth;
2020 
2021  CommandPtr cmd;
2022 
2023  unhandled.clear ();
2024  if (!alreadyConnected) while (!parseLoop ()) {} //get greeting
2025  QString greeting;
2026  if (!unhandled.isEmpty()) greeting = unhandled.first().trimmed();
2027  unhandled.clear (); //get rid of it
2028  cmd = doCommand (CommandPtr(new imapCommand ("CAPABILITY", "")));
2029 
2030  kDebug(7116) <<"IMAP4: setHost: capability";
2031  for (QStringList::const_iterator it = imapCapabilities.constBegin ();
2032  it != imapCapabilities.constEnd (); ++it)
2033  {
2034  kDebug(7116) <<"'" << (*it) <<"'";
2035  }
2036  completeQueue.removeAll (cmd);
2037 
2038  if (!hasCapability("IMAP4") && !hasCapability("IMAP4rev1"))
2039  {
2040  error(ERR_COULD_NOT_LOGIN, i18n("The server %1 supports neither "
2041  "IMAP4 nor IMAP4rev1.\nIt identified itself with: %2",
2042  myHost, greeting));
2043  closeConnection();
2044  return false;
2045  }
2046 
2047  if (metaData("nologin") == "on") return true;
2048 
2049  if (myTLS == "on" && !hasCapability(QString("STARTTLS")))
2050  {
2051  error(ERR_COULD_NOT_LOGIN, i18n("The server does not support TLS.\n"
2052  "Disable this security feature to connect unencrypted."));
2053  closeConnection();
2054  return false;
2055  }
2056  if ((myTLS == "on" /*###|| ( canUseTLS() && myTLS != "off")*/) &&
2057  hasCapability(QString("STARTTLS")))
2058  {
2059  CommandPtr cmd = doCommand (imapCommand::clientStartTLS());
2060  if (cmd->result () == "OK")
2061  {
2062  completeQueue.removeAll(cmd);
2063  if (startSsl())
2064  {
2065  kDebug(7116) <<"TLS mode has been enabled.";
2066  CommandPtr cmd2 = doCommand (CommandPtr(new imapCommand ("CAPABILITY", "")));
2067  for (QStringList::const_iterator it = imapCapabilities.constBegin ();
2068  it != imapCapabilities.constEnd (); ++it)
2069  {
2070  kDebug(7116) <<"'" << (*it) <<"'";
2071  }
2072  completeQueue.removeAll (cmd2);
2073  } else {
2074  kWarning(7116) <<"TLS mode setup has failed. Aborting.";
2075  error (ERR_COULD_NOT_LOGIN, i18n("Starting TLS failed."));
2076  closeConnection();
2077  return false;
2078  }
2079  } else completeQueue.removeAll(cmd);
2080  }
2081 
2082  if (!myAuth.isEmpty () && myAuth != "*"
2083  && !hasCapability (QString ("AUTH=") + myAuth))
2084  {
2085  error (ERR_COULD_NOT_LOGIN, i18n("The authentication method %1 is not "
2086  "supported by the server.", myAuth));
2087  closeConnection();
2088  return false;
2089  }
2090 
2091  if ( greeting.contains( QRegExp( "Cyrus IMAP4 v2.1" ) ) ) {
2092  removeCapability( "ANNOTATEMORE" );
2093  }
2094 
2095  // starting from Cyrus IMAP 2.3.9, shared seen flags are available
2096  QRegExp regExp( "Cyrus\\sIMAP[4]{0,1}\\sv(\\d+)\\.(\\d+)\\.(\\d+)", Qt::CaseInsensitive );
2097  if ( regExp.indexIn( greeting ) >= 0 ) {
2098  const int major = regExp.cap( 1 ).toInt();
2099  const int minor = regExp.cap( 2 ).toInt();
2100  const int patch = regExp.cap( 3 ).toInt();
2101  if ( major > 2 || (major == 2 && (minor > 3 || (minor == 3 && patch > 9))) ) {
2102  kDebug(7116) << "Cyrus IMAP >= 2.3.9 detected, enabling shared seen flag support";
2103  imapCapabilities.append( "x-kmail-sharedseen" );
2104  }
2105  }
2106 
2107  kDebug(7116) <<"IMAP4::makeLogin - attempting login";
2108 
2109  KIO::AuthInfo authInfo;
2110  authInfo.username = myUser;
2111  authInfo.password = myPass;
2112  authInfo.prompt = i18n ("Username and password for your IMAP account:");
2113 
2114  kDebug(7116) <<"IMAP4::makeLogin - open_PassDlg said user=" << myUser <<" pass=xx";
2115 
2116  QString resultInfo;
2117  if (myAuth.isEmpty () || myAuth == "*")
2118  {
2119  if (myUser.isEmpty () || myPass.isEmpty ()) {
2120  if(openPasswordDialog (authInfo)) {
2121  myUser = authInfo.username;
2122  myPass = authInfo.password;
2123  }
2124  }
2125  if (!clientLogin (myUser, myPass, resultInfo))
2126  error(ERR_SLAVE_DEFINED, i18n("Unable to login. Probably the "
2127  "password is wrong.\nThe server %1 replied:\n%2", myHost, resultInfo));
2128  }
2129  else
2130  {
2131  if (!clientAuthenticate (this, authInfo, myHost, myAuth, mySSL, resultInfo))
2132  error(ERR_SLAVE_DEFINED, i18n("Unable to authenticate via %1.\n" "The server %2 replied:\n%3", myAuth, myHost, resultInfo));
2133  else {
2134  myUser = authInfo.username;
2135  myPass = authInfo.password;
2136  }
2137  }
2138  if ( hasCapability("NAMESPACE") )
2139  {
2140  // get all namespaces and save the namespace - delimiter association
2141  cmd = doCommand( imapCommand::clientNamespace() );
2142  if (cmd->result () == "OK")
2143  {
2144  kDebug(7116) <<"makeLogin - registered namespaces";
2145  }
2146  completeQueue.removeAll (cmd);
2147  }
2148  // get the default delimiter (empty listing)
2149  cmd = doCommand( imapCommand::clientList("", "") );
2150  if (cmd->result () == "OK")
2151  {
2152  QList< imapList >::Iterator it = listResponses.begin();
2153  if ( it != listResponses.end() )
2154  {
2155  namespaceToDelimiter[QString()] = (*it).hierarchyDelimiter();
2156  kDebug(7116) <<"makeLogin - delimiter for empty ns='" << (*it).hierarchyDelimiter() <<"'";
2157  if ( !hasCapability("NAMESPACE") )
2158  {
2159  // server does not support namespaces
2160  QString nsentry = QString::number( 0 ) + "==" + (*it).hierarchyDelimiter();
2161  imapNamespaces.append( nsentry );
2162  }
2163  }
2164  }
2165  completeQueue.removeAll (cmd);
2166  } else {
2167  kDebug(7116) <<"makeLogin - NO login";
2168  }
2169 
2170  return getState() == ISTATE_LOGIN;
2171 }
2172 
2173 void
2174 IMAP4Protocol::parseWriteLine (const QString & aStr)
2175 {
2176  //kDebug(7116) <<"Writing:" << aStr;
2177  QByteArray writer = aStr.toUtf8();
2178  int len = writer.length();
2179 
2180  // append CRLF if necessary
2181  if (len == 0 || (writer[len - 1] != '\n')) {
2182  len += 2;
2183  writer += "\r\n";
2184  }
2185 
2186  // write it
2187  write(writer.data(), len);
2188 }
2189 
2190 QString
2191 IMAP4Protocol::getMimeType (enum IMAP_TYPE aType)
2192 {
2193  switch (aType)
2194  {
2195  case ITYPE_DIR:
2196  return "inode/directory";
2197  break;
2198 
2199  case ITYPE_BOX:
2200  return "message/digest";
2201  break;
2202 
2203  case ITYPE_DIR_AND_BOX:
2204  return "message/directory";
2205  break;
2206 
2207  case ITYPE_MSG:
2208  return "message/rfc822";
2209  break;
2210 
2211  // this should be handled by flushOutput
2212  case ITYPE_ATTACH:
2213  return "application/octet-stream";
2214  break;
2215 
2216  case ITYPE_UNKNOWN:
2217  default:
2218  return "unknown/unknown";
2219  }
2220 }
2221 
2222 
2223 
2224 void
2225 IMAP4Protocol::doListEntry (const KUrl & _url, int stretch, imapCache * cache,
2226  bool withFlags, bool withSubject)
2227 {
2228  KUrl aURL = _url;
2229  aURL.setQuery (QString());
2230  const QString encodedUrl = aURL.url(KUrl::LeaveTrailingSlash); // utf-8
2231  doListEntry(encodedUrl, stretch, cache, withFlags, withSubject);
2232 }
2233 
2234 
2235 
2236 void
2237 IMAP4Protocol::doListEntry (const QString & encodedUrl, int stretch, imapCache * cache,
2238  bool withFlags, bool withSubject)
2239 {
2240  if (cache)
2241  {
2242  UDSEntry entry;
2243 
2244  entry.clear ();
2245 
2246  const QString uid = QString::number(cache->getUid());
2247  QString tmp = uid;
2248  if (stretch > 0)
2249  {
2250  tmp = "0000000000000000" + uid;
2251  tmp = tmp.right (stretch);
2252  }
2253  if (withSubject)
2254  {
2255  mailHeader *header = cache->getHeader();
2256  if (header)
2257  tmp += ' ' + header->getSubject();
2258  }
2259  entry.insert (UDSEntry::UDS_NAME,tmp);
2260 
2261  tmp = encodedUrl; // utf-8
2262  if (tmp[tmp.length () - 1] != '/')
2263  tmp += '/';
2264  tmp += ";UID=" + uid;
2265  entry.insert( UDSEntry::UDS_URL, tmp);
2266 
2267  entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFREG);
2268 
2269  entry.insert(UDSEntry::UDS_SIZE, cache->getSize());
2270 
2271  entry.insert( UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/rfc822"));
2272 
2273  entry.insert(UDSEntry::UDS_USER,myUser);
2274 
2275  entry.insert( KIO::UDSEntry::UDS_ACCESS, (withFlags) ? cache->getFlags() : S_IRUSR | S_IXUSR | S_IWUSR);
2276 
2277  listEntry (entry, false);
2278  }
2279 }
2280 
2281 void
2282 IMAP4Protocol::doListEntry (const KUrl & _url, const QString & myBox,
2283  const imapList & item, bool appendPath)
2284 {
2285  KUrl aURL = _url;
2286  aURL.setQuery (QString());
2287  UDSEntry entry;
2288  int hdLen = item.hierarchyDelimiter().length();
2289 
2290  {
2291  // mailboxName will be appended to the path if appendPath is true
2292  QString mailboxName = item.name ();
2293 
2294  // some beautification
2295  if ( mailboxName.startsWith(myBox) && mailboxName.length() > myBox.length())
2296  {
2297  mailboxName =
2298  mailboxName.right (mailboxName.length () - myBox.length ());
2299  }
2300  if (mailboxName[0] == '/')
2301  mailboxName = mailboxName.right (mailboxName.length () - 1);
2302  if (mailboxName.left(hdLen) == item.hierarchyDelimiter())
2303  mailboxName = mailboxName.right(mailboxName.length () - hdLen);
2304  if (mailboxName.right(hdLen) == item.hierarchyDelimiter())
2305  mailboxName.truncate(mailboxName.length () - hdLen);
2306 
2307  QString tmp;
2308  if (!item.hierarchyDelimiter().isEmpty() &&
2309  mailboxName.contains(item.hierarchyDelimiter()) )
2310  tmp = mailboxName.section(item.hierarchyDelimiter(), -1);
2311  else
2312  tmp = mailboxName;
2313 
2314  // konqueror will die with an assertion failure otherwise
2315  if (tmp.isEmpty ())
2316  tmp = "..";
2317 
2318  if (!tmp.isEmpty ())
2319  {
2320  entry.insert(UDSEntry::UDS_NAME,tmp);
2321 
2322  if (!item.noSelect ())
2323  {
2324  if (!item.noInferiors ())
2325  {
2326  tmp = "message/directory";
2327  } else {
2328  tmp = "message/digest";
2329  }
2330  entry.insert(UDSEntry::UDS_MIME_TYPE,tmp);
2331 
2332  mailboxName += '/';
2333 
2334  // explicitly set this as a directory for KFileDialog
2335  entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFDIR);
2336  }
2337  else if (!item.noInferiors ())
2338  {
2339  entry.insert(UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory"));
2340  mailboxName += '/';
2341 
2342  // explicitly set this as a directory for KFileDialog
2343  entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFDIR);
2344  }
2345  else
2346  {
2347  entry.insert(UDSEntry::UDS_MIME_TYPE,QString::fromLatin1("unknown/unknown"));
2348  }
2349 
2350  QString path = aURL.path();
2351  if (appendPath)
2352  {
2353  if (path[path.length() - 1] == '/' && !path.isEmpty() && path != "/")
2354  path.truncate(path.length() - 1);
2355  if (!path.isEmpty() && path != "/"
2356  && path.right(hdLen) != item.hierarchyDelimiter()) {
2357  path += item.hierarchyDelimiter();
2358  }
2359  path += mailboxName;
2360  if (path.toUpper() == "/INBOX/") {
2361  // make sure the client can rely on INBOX
2362  path = path.toUpper();
2363  }
2364  }
2365  aURL.setPath(path);
2366  tmp = aURL.url(KUrl::LeaveTrailingSlash); // utf-8
2367  entry.insert(UDSEntry::UDS_URL, tmp);
2368 
2369  entry.insert( UDSEntry::UDS_USER, myUser);
2370 
2371  entry.insert( UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IWUSR);
2372 
2373  entry.insert( UDSEntry::UDS_EXTRA,item.attributesAsString());
2374 
2375  listEntry (entry, false);
2376  }
2377  }
2378 }
2379 
2380 enum IMAP_TYPE
2381 IMAP4Protocol::parseURL (const KUrl & _url, QString & _box,
2382  QString & _section, QString & _type, QString & _uid,
2383  QString & _validity, QString & _hierarchyDelimiter,
2384  QString & _info, bool cache)
2385 {
2386  enum IMAP_TYPE retVal;
2387  retVal = ITYPE_UNKNOWN;
2388 
2389  imapParser::parseURL (_url, _box, _section, _type, _uid, _validity, _info);
2390 // kDebug(7116) <<"URL: query - '" << KUrl::fromPercentEncoding(_url.query()) <<"'";
2391 
2392  // get the delimiter
2393  QString myNamespace = namespaceForBox( _box );
2394  kDebug(7116) <<"IMAP4::parseURL - namespace=" << myNamespace;
2395  if ( namespaceToDelimiter.contains(myNamespace) )
2396  {
2397  _hierarchyDelimiter = namespaceToDelimiter[myNamespace];
2398  kDebug(7116) <<"IMAP4::parseURL - delimiter=" << _hierarchyDelimiter;
2399  }
2400 
2401  if (!_box.isEmpty ())
2402  {
2403  kDebug(7116) <<"IMAP4::parseURL - box=" << _box;
2404 
2405  if (makeLogin ())
2406  {
2407  if (getCurrentBox () != _box ||
2408  _type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK")
2409  {
2410  if ( cache )
2411  {
2412  // assume a normal box
2413  retVal = ITYPE_DIR_AND_BOX;
2414  } else
2415  {
2416  // start a listing for the box to get the type
2417  CommandPtr cmd;
2418 
2419  cmd = doCommand (imapCommand::clientList ("", _box));
2420  if (cmd->result () == "OK")
2421  {
2422  for (QList< imapList >::Iterator it = listResponses.begin ();
2423  it != listResponses.end (); ++it)
2424  {
2425  //kDebug(7116) <<"IMAP4::parseURL - checking" << _box <<" to" << (*it).name();
2426  if (_box == (*it).name ())
2427  {
2428  if ( !(*it).hierarchyDelimiter().isEmpty() )
2429  _hierarchyDelimiter = (*it).hierarchyDelimiter();
2430  if ((*it).noSelect ())
2431  {
2432  retVal = ITYPE_DIR;
2433  }
2434  else if ((*it).noInferiors ())
2435  {
2436  retVal = ITYPE_BOX;
2437  }
2438  else
2439  {
2440  retVal = ITYPE_DIR_AND_BOX;
2441  }
2442  }
2443  }
2444  // if we got no list response for the box see if it's a prefix
2445  if ( retVal == ITYPE_UNKNOWN &&
2446  namespaceToDelimiter.contains(_box) ) {
2447  retVal = ITYPE_DIR;
2448  }
2449  } else {
2450  kDebug(7116) <<"IMAP4::parseURL - got error for" << _box;
2451  }
2452  completeQueue.removeAll (cmd);
2453  } // cache
2454  }
2455  else // current == box
2456  {
2457  retVal = ITYPE_BOX;
2458  }
2459  }
2460  else
2461  kDebug(7116) <<"IMAP4::parseURL: no login!";
2462 
2463  }
2464  else // empty box
2465  {
2466  // the root is just a dir
2467  kDebug(7116) <<"IMAP4: parseURL: box [root]";
2468  retVal = ITYPE_DIR;
2469  }
2470 
2471  // see if it is a real sequence or a simple uid
2472  if (retVal == ITYPE_BOX || retVal == ITYPE_DIR_AND_BOX)
2473  {
2474  if (!_uid.isEmpty ())
2475  {
2476  if ( !_uid.contains(':') && !_uid.contains(',') && !_uid.contains('*') )
2477  retVal = ITYPE_MSG;
2478  }
2479  }
2480  if (retVal == ITYPE_MSG)
2481  {
2482  if ( ( _section.contains("BODY.PEEK[", Qt::CaseInsensitive) ||
2483  _section.contains("BODY[", Qt::CaseInsensitive) ) &&
2484  !_section.contains(".MIME") &&
2485  !_section.contains(".HEADER") )
2486  retVal = ITYPE_ATTACH;
2487  }
2488  if ( _hierarchyDelimiter.isEmpty() &&
2489  (_type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK") )
2490  {
2491  // this shouldn't happen but when the delimiter is really empty
2492  // we try to reconstruct it from the URL
2493  if (!_box.isEmpty())
2494  {
2495  int start = _url.path().lastIndexOf(_box);
2496  if (start != -1)
2497  _hierarchyDelimiter = _url.path().mid(start-1, start);
2498  kDebug(7116) <<"IMAP4::parseURL - reconstructed delimiter:" << _hierarchyDelimiter
2499  << "from URL" << _url.path();
2500  }
2501  if (_hierarchyDelimiter.isEmpty())
2502  _hierarchyDelimiter = '/';
2503  }
2504  kDebug(7116) <<"IMAP4::parseURL - return" << retVal;
2505 
2506  return retVal;
2507 }
2508 
2509 int
2510 IMAP4Protocol::outputLine (const QByteArray & _str, int len)
2511 {
2512  if (len == -1) {
2513  len = _str.length();
2514  }
2515 
2516  if (cacheOutput)
2517  {
2518  if ( !outputBuffer.isOpen() ) {
2519  outputBuffer.open(QIODevice::WriteOnly);
2520  }
2521  outputBuffer.seek( outputBufferIndex );
2522  outputBuffer.write(_str.data(), len);
2523  outputBufferIndex += len;
2524  return 0;
2525  }
2526 
2527  QByteArray temp;
2528  bool relay = relayEnabled;
2529 
2530  relayEnabled = true;
2531  temp = QByteArray::fromRawData (_str.data (), len);
2532  parseRelay (temp);
2533  temp.clear();
2534 
2535  relayEnabled = relay;
2536  return 0;
2537 }
2538 
2539 void IMAP4Protocol::flushOutput(const QString &contentEncoding)
2540 {
2541  // send out cached data to the application
2542  if (outputBufferIndex == 0)
2543  return;
2544  outputBuffer.close();
2545  outputCache.resize(outputBufferIndex);
2546  if (decodeContent)
2547  {
2548  // get the coding from the MIME header
2549  QByteArray decoded;
2550  if ( contentEncoding.startsWith(QLatin1String("quoted-printable"), Qt::CaseInsensitive) )
2551  decoded = KCodecs::quotedPrintableDecode(outputCache);
2552  else if ( contentEncoding.startsWith(QLatin1String("base64"), Qt::CaseInsensitive) )
2553  decoded = QByteArray::fromBase64( outputCache );
2554  else
2555  decoded = outputCache;
2556 
2557  QString mimetype = KMimeType::findByContent( decoded )->name();
2558  kDebug(7116) <<"IMAP4::flushOutput - mimeType" << mimetype;
2559  mimeType(mimetype);
2560  decodeContent = false;
2561  data( decoded );
2562  } else {
2563  data( outputCache );
2564  }
2565  mProcessedSize += outputBufferIndex;
2566  processedSize( mProcessedSize );
2567  outputBufferIndex = 0;
2568  outputCache[0] = '\0';
2569  outputBuffer.setBuffer(&outputCache);
2570 }
2571 
2572 ssize_t IMAP4Protocol::myRead(void *data, ssize_t len)
2573 {
2574  if (readBufferLen)
2575  {
2576  ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen;
2577  memcpy(data, readBuffer, copyLen);
2578  readBufferLen -= copyLen;
2579  if (readBufferLen) memmove(readBuffer, &readBuffer[copyLen], readBufferLen);
2580  return copyLen;
2581  }
2582  if (!isConnected()) return 0;
2583  waitForResponse( responseTimeout() );
2584  return read((char*)data, len);
2585 }
2586 
2587 bool
2588 IMAP4Protocol::assureBox (const QString & aBox, bool readonly)
2589 {
2590  if (aBox.isEmpty()) return false;
2591 
2592  CommandPtr cmd;
2593 
2594  if (aBox != getCurrentBox () || (!getSelected().readWrite() && !readonly))
2595  {
2596  // open the box with the appropriate mode
2597  kDebug(7116) <<"IMAP4Protocol::assureBox - opening box";
2598  selectInfo = imapInfo();
2599  cmd = doCommand (imapCommand::clientSelect (aBox, readonly));
2600  bool ok = cmd->result() == "OK";
2601  QString cmdInfo = cmd->resultInfo();
2602  completeQueue.removeAll (cmd);
2603 
2604  if (!ok)
2605  {
2606  bool found = false;
2607  cmd = doCommand (imapCommand::clientList ("", aBox));
2608  if (cmd->result () == "OK")
2609  {
2610  for (QList< imapList >::Iterator it = listResponses.begin ();
2611  it != listResponses.end (); ++it)
2612  {
2613  if (aBox == (*it).name ()) found = true;
2614  }
2615  }
2616  completeQueue.removeAll (cmd);
2617  if (found) {
2618  if ( cmdInfo.contains("permission", Qt::CaseInsensitive) ) {
2619  // not allowed to enter this folder
2620  error(ERR_ACCESS_DENIED, cmdInfo);
2621  } else {
2622  error(ERR_SLAVE_DEFINED, i18n("Unable to open folder %1. The server replied: %2", aBox, cmdInfo));
2623  }
2624  } else {
2625  error(KIO::ERR_DOES_NOT_EXIST, aBox);
2626  }
2627  return false;
2628  }
2629  }
2630  else
2631  {
2632  // Give the server a chance to deliver updates every ten seconds.
2633  // Doing this means a server roundtrip and since assureBox is called
2634  // after every mail, we do it with a timeout.
2635  kDebug(7116) <<"IMAP4Protocol::assureBox - reusing box";
2636  if ( mTimeOfLastNoop.secsTo( QDateTime::currentDateTime() ) > 10 ) {
2637  cmd = doCommand (imapCommand::clientNoop ());
2638  completeQueue.removeAll (cmd);
2639  mTimeOfLastNoop = QDateTime::currentDateTime();
2640  kDebug(7116) <<"IMAP4Protocol::assureBox - noop timer fired";
2641  }
2642  }
2643 
2644  // if it is the mode we want
2645  if (!getSelected().readWrite() && !readonly)
2646  {
2647  error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, aBox);
2648  return false;
2649  }
2650 
2651  return true;
2652 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jan 5 2013 19:44:15 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kioslave/imap4

Skip menu "kioslave/imap4"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.9.5 API Reference

Skip menu "kdepimlibs-4.9.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal