summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-25 23:43:13 (GMT)
committerChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-25 23:43:13 (GMT)
commite21e62c14f5aae99d46643065fcb86e2e3abe230 (patch)
tree71d79d2de0b0f82f8b0a6ca3b8b09308d198ed33
parent172803b38237e38a494aca62fffda918e5799d20 (diff)
downloadlibcalendaring-e21e62c14f5aae99d46643065fcb86e2e3abe230.tar.gz
initial import of kimap from
commit b54a325116b194da090f900c9a538710759eb303 Author: Stephen Kelly <steveire@gmail.com> Date: Sun May 6 20:44:53 2012 +0200 Revert "Port to const QRegExp API." This reverts commit 0ca0dfc7e0ca8095efd0b060d1d5e26ac9ceb379. The qtbase commit requiring this was reverted.
-rw-r--r--kimap/.krazy1
-rw-r--r--kimap/CMakeLists.txt67
-rw-r--r--kimap/Mainpage.dox30
-rwxr-xr-xkimap/Messages.sh2
-rw-r--r--kimap/acl.cpp127
-rw-r--r--kimap/acl.h127
-rw-r--r--kimap/acljobbase.cpp99
-rw-r--r--kimap/acljobbase.h81
-rw-r--r--kimap/acljobbase_p.h53
-rw-r--r--kimap/appendjob.cpp138
-rw-r--r--kimap/appendjob.h111
-rw-r--r--kimap/capabilitiesjob.cpp77
-rw-r--r--kimap/capabilitiesjob.h78
-rw-r--r--kimap/closejob.cpp54
-rw-r--r--kimap/closejob.h69
-rw-r--r--kimap/copyjob.cpp135
-rw-r--r--kimap/copyjob.h127
-rw-r--r--kimap/createjob.cpp71
-rw-r--r--kimap/createjob.h76
-rw-r--r--kimap/deleteacljob.cpp72
-rw-r--r--kimap/deleteacljob.h74
-rw-r--r--kimap/deletejob.cpp70
-rw-r--r--kimap/deletejob.h72
-rw-r--r--kimap/expungejob.cpp83
-rw-r--r--kimap/expungejob.h63
-rw-r--r--kimap/fetchjob.cpp531
-rw-r--r--kimap/fetchjob.h303
-rw-r--r--kimap/getacljob.cpp113
-rw-r--r--kimap/getacljob.h141
-rw-r--r--kimap/getmetadatajob.cpp211
-rw-r--r--kimap/getmetadatajob.h196
-rw-r--r--kimap/getquotajob.cpp83
-rw-r--r--kimap/getquotajob.h80
-rw-r--r--kimap/getquotarootjob.cpp185
-rw-r--r--kimap/getquotarootjob.h124
-rw-r--r--kimap/idlejob.cpp160
-rw-r--r--kimap/idlejob.h132
-rw-r--r--kimap/imapset.cpp322
-rw-r--r--kimap/imapset.h239
-rw-r--r--kimap/imapstreamparser.cpp540
-rw-r--r--kimap/imapstreamparser.h216
-rw-r--r--kimap/job.cpp94
-rw-r--r--kimap/job.h69
-rw-r--r--kimap/job_p.h53
-rw-r--r--kimap/kimap_export.h39
-rw-r--r--kimap/listjob.cpp220
-rw-r--r--kimap/listjob.h101
-rw-r--r--kimap/listrightsjob.cpp107
-rw-r--r--kimap/listrightsjob.h122
-rw-r--r--kimap/loginjob.cpp533
-rw-r--r--kimap/loginjob.h113
-rw-r--r--kimap/logoutjob.cpp59
-rw-r--r--kimap/logoutjob.h50
-rw-r--r--kimap/message_p.h97
-rw-r--r--kimap/metadatajobbase.cpp71
-rw-r--r--kimap/metadatajobbase.h126
-rw-r--r--kimap/metadatajobbase_p.h43
-rw-r--r--kimap/myrightsjob.cpp83
-rw-r--r--kimap/myrightsjob.h93
-rw-r--r--kimap/namespacejob.cpp142
-rw-r--r--kimap/namespacejob.h58
-rw-r--r--kimap/quotajobbase.cpp91
-rw-r--r--kimap/quotajobbase.h99
-rw-r--r--kimap/quotajobbase_p.h42
-rw-r--r--kimap/renamejob.cpp85
-rw-r--r--kimap/renamejob.h63
-rw-r--r--kimap/rfccodecs.cpp659
-rw-r--r--kimap/rfccodecs.h139
-rw-r--r--kimap/searchjob.cpp316
-rw-r--r--kimap/searchjob.h162
-rw-r--r--kimap/selectjob.cpp194
-rw-r--r--kimap/selectjob.h67
-rw-r--r--kimap/session.cpp497
-rw-r--r--kimap/session.h156
-rw-r--r--kimap/session_p.h120
-rw-r--r--kimap/sessionlogger.cpp62
-rw-r--r--kimap/sessionlogger_p.h48
-rw-r--r--kimap/sessionthread.cpp232
-rw-r--r--kimap/sessionthread_p.h85
-rw-r--r--kimap/sessionuiproxy.h68
-rw-r--r--kimap/setacljob.cpp81
-rw-r--r--kimap/setacljob.h113
-rw-r--r--kimap/setmetadatajob.cpp153
-rw-r--r--kimap/setmetadatajob.h216
-rw-r--r--kimap/setquotajob.cpp104
-rw-r--r--kimap/setquotajob.h103
-rw-r--r--kimap/storejob.cpp194
-rw-r--r--kimap/storejob.h75
-rw-r--r--kimap/subscribejob.cpp71
-rw-r--r--kimap/subscribejob.h52
-rw-r--r--kimap/tests/CMakeLists.txt52
-rw-r--r--kimap/tests/capabilitiesjobtest.cpp99
-rw-r--r--kimap/tests/createjobtest.cpp88
-rw-r--r--kimap/tests/deletejobtest.cpp95
-rw-r--r--kimap/tests/fakeserverscenario.log6
-rw-r--r--kimap/tests/fakeservertest.cpp68
-rw-r--r--kimap/tests/fetchjobtest.cpp296
-rw-r--r--kimap/tests/idlejobtest.cpp216
-rw-r--r--kimap/tests/imapsettest.cpp76
-rw-r--r--kimap/tests/kimaptest/CMakeLists.txt24
-rw-r--r--kimap/tests/kimaptest/fakeserver.cpp223
-rw-r--r--kimap/tests/kimaptest/fakeserver.h222
-rw-r--r--kimap/tests/kimaptest/mockjob.cpp79
-rw-r--r--kimap/tests/kimaptest/mockjob.h75
-rw-r--r--kimap/tests/listjobtest.cpp237
-rw-r--r--kimap/tests/loginjobtest.cpp154
-rw-r--r--kimap/tests/logoutjobtest.cpp81
-rw-r--r--kimap/tests/quotarootjobtest.cpp187
-rw-r--r--kimap/tests/renamejobtest.cpp91
-rw-r--r--kimap/tests/selectjobtest.cpp147
-rw-r--r--kimap/tests/storejobtest.cpp100
-rw-r--r--kimap/tests/subscribejobtest.cpp87
-rw-r--r--kimap/tests/testimapidle.cpp130
-rw-r--r--kimap/tests/testimapserver.cpp604
-rw-r--r--kimap/tests/testrfccodecs.cpp79
-rw-r--r--kimap/tests/testrfccodecs.h35
-rw-r--r--kimap/tests/testsession.cpp330
-rw-r--r--kimap/tests/unsubscribejobtest.cpp87
-rw-r--r--kimap/unsubscribejob.cpp71
-rw-r--r--kimap/unsubscribejob.h52
120 files changed, 16244 insertions, 0 deletions
diff --git a/kimap/.krazy b/kimap/.krazy
new file mode 100644
index 0000000..0b16e7f
--- /dev/null
+++ b/kimap/.krazy
@@ -0,0 +1 @@
+SKIP /tests/
diff --git a/kimap/CMakeLists.txt b/kimap/CMakeLists.txt
new file mode 100644
index 0000000..ab3a5ca
--- /dev/null
+++ b/kimap/CMakeLists.txt
@@ -0,0 +1,67 @@
+project(kimap)
+
+add_definitions( -DKDE_DEFAULT_DEBUG_AREA=5327 )
+
+add_subdirectory( tests )
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
+
+set(kimap_LIB_SRCS
+ acl.cpp
+ imapset.cpp
+ imapstreamparser.cpp
+ job.cpp
+ appendjob.cpp
+ capabilitiesjob.cpp
+ fetchjob.cpp
+ idlejob.cpp
+ listjob.cpp
+ loginjob.cpp
+ logoutjob.cpp
+ namespacejob.cpp
+ rfccodecs.cpp
+ selectjob.cpp
+ session.cpp
+ sessionlogger.cpp
+ sessionthread.cpp
+ closejob.cpp
+ expungejob.cpp
+ deletejob.cpp
+ createjob.cpp
+ subscribejob.cpp
+ unsubscribejob.cpp
+ renamejob.cpp
+ storejob.cpp
+ copyjob.cpp
+ searchjob.cpp
+ acljobbase.cpp
+ setacljob.cpp
+ getacljob.cpp
+ deleteacljob.cpp
+ myrightsjob.cpp
+ listrightsjob.cpp
+ quotajobbase.cpp
+ setquotajob.cpp
+ getquotajob.cpp
+ getquotarootjob.cpp
+ metadatajobbase.cpp
+ setmetadatajob.cpp
+ getmetadatajob.cpp
+)
+
+kde4_add_library(kimap ${LIBRARY_TYPE} ${kimap_LIB_SRCS})
+
+target_link_libraries(kimap ${KDE4_KDECORE_LIBS} ${QT_QTNETWORK_LIBRARY} kmime ${SASL2_LIBRARIES})
+
+include_directories( ${CMAKE_SOURCE_DIR}/kioslave ${SASL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} )
+
+set_target_properties(kimap PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} )
+install(TARGETS kimap EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS})
+
+########### install files ###############
+
+install( FILES kimap_export.h acl.h imapset.h job.h appendjob.h capabilitiesjob.h fetchjob.h idlejob.h listjob.h loginjob.h logoutjob.h namespacejob.h rfccodecs.h
+ selectjob.h closejob.h expungejob.h deletejob.h createjob.h subscribejob.h unsubscribejob.h renamejob.h
+ session.h sessionuiproxy.h storejob.h copyjob.h searchjob.h acljobbase.h setacljob.h getacljob.h deleteacljob.h
+ myrightsjob.h listrightsjob.h quotajobbase.h setquotajob.h getquotajob.h getquotarootjob.h metadatajobbase.h setmetadatajob.h getmetadatajob.h
+ DESTINATION ${INCLUDE_INSTALL_DIR}/kimap COMPONENT Devel)
diff --git a/kimap/Mainpage.dox b/kimap/Mainpage.dox
new file mode 100644
index 0000000..6f0e3eb
--- /dev/null
+++ b/kimap/Mainpage.dox
@@ -0,0 +1,30 @@
+/*!
+ * @mainpage KIMAP - a job-based API for interacting with IMAP servers
+ *
+ * @section purpose Purpose
+ *
+ * This library provides a job-based API for interacting with an IMAP4rev1 server.
+ * It manages connections, encryption and parameter quoting and encoding, but
+ * otherwise provides quite a low-level interface to the protocol. This library
+ * does not implement an IMAP client; it merely makes it easier to do so.
+ *
+ * Users should be familiar with
+ * <a href="http://www.apps.ietf.org/rfc/rfc3501.html" title="IMAP 4rev1">RFC 3501</a>,
+ * as well as <a href="http://www.iana.org/assignments/imap4-capabilities">other related RFCs</a>,
+ * although the library hides some of the nastier details like the encoding and quoting of
+ * strings.
+ *
+ * @section desc Description
+ *
+ * @authors
+ * The major authors of this library are:\n
+ * Sven Carstens \<s.carstens@gmx.de\>
+ *
+ * @maintainers
+ * none at the moment
+ *
+ * @licenses
+ * @lgpl
+ */
+
+// DOXYGEN_PROJECTNAME=KIMAP Library
diff --git a/kimap/Messages.sh b/kimap/Messages.sh
new file mode 100755
index 0000000..6881e62
--- /dev/null
+++ b/kimap/Messages.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+$XGETTEXT *.cpp -o $podir/libkimap.pot
diff --git a/kimap/acl.cpp b/kimap/acl.cpp
new file mode 100644
index 0000000..a0d9741
--- /dev/null
+++ b/kimap/acl.cpp
@@ -0,0 +1,127 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "acl.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QMap>
+#include <KDE/KGlobal>
+
+namespace KIMAP {
+namespace Acl {
+
+class RightsMap
+{
+ public:
+ RightsMap()
+ {
+ map['l'] = Lookup;
+ map['r'] = Read;
+ map['s'] = KeepSeen;
+ map['w'] = Write;
+ map['i'] = Insert;
+ map['p'] = Post;
+ map['c'] = Create; //TODO: obsolete, keep it?
+ map['d'] = Delete; //TODO: obsolete, keep it?
+ map['k'] = CreateMailbox;
+ map['x'] = DeleteMailbox;
+ map['t'] = DeleteMessage;
+ map['e'] = Expunge;
+ map['a'] = Admin;
+ map['n'] = WriteShared;
+ map['0'] = Custom0;
+ map['1'] = Custom1;
+ map['2'] = Custom2;
+ map['3'] = Custom3;
+ map['4'] = Custom4;
+ map['5'] = Custom5;
+ map['6'] = Custom6;
+ map['7'] = Custom7;
+ map['8'] = Custom8;
+ map['9'] = Custom9;
+ }
+
+ QMap<char, Right> map;
+};
+
+Q_GLOBAL_STATIC(RightsMap, globalRights)
+
+}
+}
+
+KIMAP::Acl::Rights KIMAP::Acl::rightsFromString( const QByteArray &string )
+{
+ Rights result;
+
+ if ( string.isEmpty() )
+ return result;
+
+ int pos = 0;
+ if ( string[0] == '+' || string[0]== '-') { // Skip modifier if any
+ pos++;
+ }
+
+ for ( int i = pos; i < string.size(); i++ ) {
+ if ( globalRights()->map.contains( string[i] ) ) {
+ result|= globalRights()->map[string[i]];
+ }
+ }
+
+ return result;
+}
+
+QByteArray KIMAP::Acl::rightsToString( Rights rights )
+{
+ QByteArray result;
+
+ for ( int right = Lookup; right<=Custom9; right<<=1 ) {
+ if ( rights & right ) {
+ result+= globalRights()->map.key( (Right)right );
+ }
+ }
+
+ return result;
+}
+
+KIMAP::Acl::Rights KIMAP::Acl::normalizedRights( KIMAP::Acl::Rights rights )
+{
+ Rights normalized = rights;
+ if ( normalized & Create ) {
+ normalized |= ( CreateMailbox | DeleteMailbox );
+ normalized &= ~Create;
+ }
+ if ( normalized & Delete ) {
+ normalized |= ( DeleteMessage | Expunge );
+ normalized &= ~Delete;
+ }
+ return normalized;
+}
+
+KIMAP::Acl::Rights KIMAP::Acl::denormalizedRights( KIMAP::Acl::Rights rights )
+{
+ Rights denormalized = normalizedRights( rights );
+ if ( denormalized & ( CreateMailbox | DeleteMailbox ) ) {
+ denormalized |= Create;
+ }
+ if ( denormalized & ( DeleteMessage | Expunge ) ) {
+ denormalized |= Delete;
+ }
+ return denormalized;
+}
+
diff --git a/kimap/acl.h b/kimap/acl.h
new file mode 100644
index 0000000..40e738f
--- /dev/null
+++ b/kimap/acl.h
@@ -0,0 +1,127 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_ACL_H
+#define KIMAP_ACL_H
+
+#include "kimap_export.h"
+
+namespace KIMAP {
+
+/**
+ * Operations for dealing with mailbox permissions.
+ */
+namespace Acl {
+
+/**
+ * Possible rights that can be held on a mailbox
+ */
+enum Right {
+ None = 0x000000,
+ /** Mailbox is visible to LIST/LSUB commands, SUBSCRIBE mailbox */
+ Lookup = 0x000001,
+ /** SELECT the mailbox, perform STATUS */
+ Read = 0x000002,
+ /** Set or clear the \Seen flag on messages in the mailbox, and keep it across sessions */
+ KeepSeen = 0x000004,
+ /** Set or clear flags other than \Seen and \Deleted on messages in the mailbox */
+ Write = 0x000008,
+ /** Perform APPEND and COPY with the mailbox as the target */
+ Insert = 0x000010,
+ /** Send mail to the submission address for the mailbox
+ *
+ * Note: this is not enforced by IMAP4, but is purely advisory.
+ */
+ Post = 0x000020,
+ /** Obsolete as of RFC 4314, replaced by CreateMailbox and DeleteMailbox */
+ Create = 0x000040,
+ /** Create new child mailboxes, or move a mailbox with this mailbox as the new parent
+ *
+ * Note that what constitutes a "child" mailbox is implementation-defined, but
+ * . or / are usually used as separaters.
+ */
+ CreateMailbox = 0x000080,
+ /** Delete or move the mailbox */
+ DeleteMailbox = 0x000100,
+ /** Set or clear the \Deleted flag on messages in the mailbox */
+ DeleteMessage = 0x000200,
+ /** Obsolete as of RFC 4314, replaced by DeleteMessage and Expunge*/
+ Delete = 0x000400,
+ /** View and modify the access control list for the mailbox */
+ Admin = 0x000800,
+ /** Expunge the messages in this mailbox
+ *
+ * Note that if this right is not held on a mailbox, closing the mailbox
+ * (see CloseJob) will succeed, but will not expunge the messages.
+ */
+ Expunge = 0x001000,
+ /** Write shared annotations
+ *
+ * See <a href="http://www.apps.ietf.org/rfc/rfc5257.html" title="IMAP ANNOTATE extension">RFC
+ * 5257</a>. Only supported by servers that implement the ANNOTATE extension.
+ */
+ WriteShared = 0x002000,
+ Custom0 = 0x004000, /**< Server-specific right 0 */
+ Custom1 = 0x008000, /**< Server-specific right 1 */
+ Custom2 = 0x010000, /**< Server-specific right 2 */
+ Custom3 = 0x020000, /**< Server-specific right 3 */
+ Custom4 = 0x040000, /**< Server-specific right 4 */
+ Custom5 = 0x080000, /**< Server-specific right 5 */
+ Custom6 = 0x100000, /**< Server-specific right 6 */
+ Custom7 = 0x200000, /**< Server-specific right 7 */
+ Custom8 = 0x400000, /**< Server-specific right 8 */
+ Custom9 = 0x800000 /**< Server-specific right 9 */
+};
+
+Q_DECLARE_FLAGS(Rights, Right)
+
+/**
+ * Returns a rights mask that has no obsolete members anymore, i.e. obsolete flags are removed and
+ * replaced by their successors.
+ * @since 4.6
+ */
+KIMAP_EXPORT Rights normalizedRights( Rights rights );
+
+/**
+ * Returns a rights mask that contains both obsolete and new flags if one of them is set.
+ * @since 4.6
+ */
+KIMAP_EXPORT Rights denormalizedRights( Rights rights );
+
+/**
+ * Convert a set of rights into text format
+ *
+ * No modifier flag ('+' or '-') will be included.
+ */
+KIMAP_EXPORT QByteArray rightsToString( Rights rights );
+/**
+ * Convert the text form of a set of rights into a Rights bitflag
+ *
+ * Modifier flags ('+' and '-') are ignored, as are any unknown
+ * characters. This method will not complain if you give it
+ * something that is not a list of rights.
+ */
+KIMAP_EXPORT Rights rightsFromString( const QByteArray &string );
+
+}
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( KIMAP::Acl::Rights )
+
+#endif
diff --git a/kimap/acljobbase.cpp b/kimap/acljobbase.cpp
new file mode 100644
index 0000000..7a83627
--- /dev/null
+++ b/kimap/acljobbase.cpp
@@ -0,0 +1,99 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "acljobbase.h"
+#include "acljobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+using namespace KIMAP;
+
+void AclJobBasePrivate::setIdentifier( const QByteArray &identifier )
+{
+ id = identifier;
+}
+
+QByteArray AclJobBasePrivate::identifier() const
+{
+ return id;
+}
+
+bool AclJobBasePrivate::hasRightEnabled(Acl::Right right)
+{
+ return rightList & right;
+}
+
+void AclJobBasePrivate::setRights(const QByteArray& rights)
+{
+ switch ( rights[0] ) {
+ case '+':
+ modifier = AclJobBase::Add;
+ break;
+ case '-':
+ modifier = AclJobBase::Remove;
+ break;
+ default:
+ modifier = AclJobBase::Change;
+ break;
+ }
+
+ rightList = Acl::rightsFromString(rights);
+}
+
+void AclJobBasePrivate::setRights(AclJobBase::AclModifier _modifier, Acl::Rights rights)
+{
+ modifier = _modifier;
+ // XXX: [alexmerry, 2010-07-24]: this is REALLY unintuitive behaviour
+ rightList|= rights;
+}
+
+
+
+AclJobBase::AclJobBase( Session *session )
+ : Job( *new AclJobBasePrivate(session, i18n("AclJobBase")) )
+{
+}
+
+
+AclJobBase::AclJobBase( JobPrivate &dd )
+ : Job(dd)
+{
+
+}
+
+AclJobBase::~AclJobBase()
+{
+}
+
+
+void AclJobBase::setMailBox( const QString &mailBox )
+{
+ Q_D(AclJobBase);
+ d->mailBox = mailBox;
+}
+
+QString AclJobBase::mailBox() const
+{
+ Q_D(const AclJobBase);
+ return d->mailBox;
+}
+
diff --git a/kimap/acljobbase.h b/kimap/acljobbase.h
new file mode 100644
index 0000000..39616a2
--- /dev/null
+++ b/kimap/acljobbase.h
@@ -0,0 +1,81 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_ACLJOBBASE_H
+#define KIMAP_ACLJOBBASE_H
+
+#include "kimap_export.h"
+
+#include "acl.h"
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class AclJobBasePrivate;
+
+/**
+ * Base class for jobs that operate on mailbox ACLs
+ *
+ * Provides support for the IMAP ACL extension, as defined by
+ * <a href="http://www.apps.ietf.org/rfc/rfc4314.html" title="IMAP ACL extension">RFC 4314</a>.
+ *
+ * This class cannot be used directly, you must subclass it and reimplement
+ * at least the doStart() method.
+*/
+class KIMAP_EXPORT AclJobBase : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(AclJobBase)
+
+ friend class SessionPrivate;
+
+ public:
+ AclJobBase( Session *session );
+ virtual ~AclJobBase();
+
+ /**
+ * Used when subclassing to specify how the ACL will be modified.
+ */
+ enum AclModifier {
+ Add = 0,
+ Remove,
+ Change
+ };
+
+ /**
+ * Set the mailbox to act on
+ *
+ * @param mailBox the name of an existing mailbox
+ */
+ void setMailBox( const QString &mailBox );
+ /**
+ * The mailbox that will be acted upon.
+ */
+ QString mailBox() const;
+
+ protected:
+ explicit AclJobBase( JobPrivate &dd );
+
+};
+
+}
+
+#endif
diff --git a/kimap/acljobbase_p.h b/kimap/acljobbase_p.h
new file mode 100644
index 0000000..67692bb
--- /dev/null
+++ b/kimap/acljobbase_p.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_ACLJOBBASE_P_H
+#define KIMAP_ACLJOBBASE_P_H
+
+#include "acljobbase.h"
+#include "job_p.h"
+#include "session.h"
+#include <KDE/KLocale>
+
+namespace KIMAP
+{
+ class AclJobBasePrivate : public JobPrivate
+ {
+ public:
+ AclJobBasePrivate( Session *session, const QString& name ) : JobPrivate(session, name), rightList(Acl::None), modifier(AclJobBase::Change)
+ {
+ }
+ ~AclJobBasePrivate() { }
+
+ void setIdentifier( const QByteArray &identifier );
+ QByteArray identifier() const;
+
+ bool hasRightEnabled(Acl::Right right);
+
+ void setRights(const QByteArray& rights);
+ void setRights(AclJobBase::AclModifier modifier, Acl::Rights rights);
+
+ QString mailBox;
+ QByteArray id;
+ Acl::Rights rightList;
+ AclJobBase::AclModifier modifier;
+ };
+}
+
+#endif
diff --git a/kimap/appendjob.cpp b/kimap/appendjob.cpp
new file mode 100644
index 0000000..2125f17
--- /dev/null
+++ b/kimap/appendjob.cpp
@@ -0,0 +1,138 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "appendjob.h"
+
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class AppendJobPrivate : public JobPrivate
+ {
+ public:
+ AppendJobPrivate( Session *session, const QString& name ) : JobPrivate( session, name ), uid( 0 ) { }
+ ~AppendJobPrivate() { }
+
+ QString mailBox;
+ QList<QByteArray> flags;
+ QByteArray content;
+ qint64 uid;
+ };
+}
+
+using namespace KIMAP;
+
+AppendJob::AppendJob( Session *session )
+ : Job( *new AppendJobPrivate(session, i18n("Append")) )
+{
+}
+
+AppendJob::~AppendJob()
+{
+}
+
+void AppendJob::setMailBox( const QString &mailBox )
+{
+ Q_D(AppendJob);
+ d->mailBox = mailBox;
+}
+
+QString AppendJob::mailBox() const
+{
+ Q_D(const AppendJob);
+ return d->mailBox;
+}
+
+void AppendJob::setFlags( const QList<QByteArray> &flags)
+{
+ Q_D(AppendJob);
+ d->flags = flags;
+}
+
+QList<QByteArray> AppendJob::flags() const
+{
+ Q_D(const AppendJob);
+ return d->flags;
+}
+
+void AppendJob::setContent( const QByteArray &content )
+{
+ Q_D(AppendJob);
+ d->content = content;
+}
+
+QByteArray AppendJob::content() const
+{
+ Q_D(const AppendJob);
+ return d->content;
+}
+
+qint64 AppendJob::uid() const
+{
+ Q_D(const AppendJob);
+ return d->uid;
+}
+
+void AppendJob::doStart()
+{
+ Q_D(AppendJob);
+
+ QByteArray parameters = '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"';
+
+ if ( !d->flags.isEmpty() ) {
+ parameters+=" (";
+ foreach ( const QByteArray &flag, d->flags ) {
+ parameters+= flag+' ';
+ }
+ parameters.chop(1);
+ parameters+=')';
+ }
+
+ parameters+=" {"+QByteArray::number(d->content.size())+'}';
+
+ d->tags << d->sessionInternal()->sendCommand( "APPEND", parameters );
+}
+
+void AppendJob::handleResponse( const Message &response )
+{
+ Q_D(AppendJob);
+
+ for ( QList<Message::Part>::ConstIterator it = response.responseCode.begin();
+ it != response.responseCode.end(); ++it ) {
+ if ( it->toString()=="APPENDUID" ) {
+ it = it + 2;
+ if ( it != response.responseCode.end() ) {
+ d->uid = it->toString().toLongLong();
+ }
+ break;
+ }
+ }
+
+ if (handleErrorReplies(response) == NotHandled ) {
+ if ( response.content[0].toString() == "+" ) {
+ d->sessionInternal()->sendData( d->content );
+ }
+ }
+}
+
diff --git a/kimap/appendjob.h b/kimap/appendjob.h
new file mode 100644
index 0000000..9fdc2bd
--- /dev/null
+++ b/kimap/appendjob.h
@@ -0,0 +1,111 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_APPENDJOB_H
+#define KIMAP_APPENDJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class AppendJobPrivate;
+
+/**
+ * Appends a message to a mailbox.
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * If the server supports ACLs, the user will need the
+ * Acl::Insert right on the mailbox.
+ */
+class KIMAP_EXPORT AppendJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(AppendJob)
+
+ friend class SessionPrivate;
+
+ public:
+ AppendJob( Session *session );
+ virtual ~AppendJob();
+
+ /**
+ * Set the mailbox to append the message to.
+ *
+ * If the mailbox does not exist, it will not automatically
+ * be created and the command will fail.
+ *
+ * @param mailBox the (unquoted) name of the mailbox
+ */
+ void setMailBox( const QString &mailBox );
+ /**
+ * The mailbox that the message will be appended to.
+ */
+ QString mailBox() const;
+
+ /**
+ * Set the flags that should be applied to the appended message.
+ *
+ * @param flags a list of flags
+ */
+ void setFlags( const QList<QByteArray> &flags);
+ /**
+ * The flags that will be set on the appended message.
+ */
+ QList<QByteArray> flags() const;
+
+ /**
+ * The content of the message.
+ *
+ * This should be in RFC-2822 format, although some required header
+ * lines may be omitted in certain cases, for example when appending
+ * to a Drafts folder.
+ *
+ * @param content usually an RFC-2822 message
+ */
+ void setContent( const QByteArray &content );
+ /**
+ * The content that the message will have.
+ */
+ QByteArray content() const;
+
+ /**
+ * The UID of the new message.
+ *
+ * This will be zero if it is unknown.
+ *
+ * The UID will not be known until the job has been successfully
+ * executed, and it will only be known at all if the server
+ * supports the UIDPLUS extension (RFC 4315).
+ */
+ qint64 uid() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+};
+
+}
+
+#endif
diff --git a/kimap/capabilitiesjob.cpp b/kimap/capabilitiesjob.cpp
new file mode 100644
index 0000000..3e7b2ff
--- /dev/null
+++ b/kimap/capabilitiesjob.cpp
@@ -0,0 +1,77 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "capabilitiesjob.h"
+
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class CapabilitiesJobPrivate : public JobPrivate
+ {
+ public:
+ CapabilitiesJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~CapabilitiesJobPrivate() { }
+
+ QStringList capabilities;
+ };
+}
+
+using namespace KIMAP;
+
+CapabilitiesJob::CapabilitiesJob( Session *session )
+ : Job( *new CapabilitiesJobPrivate(session, i18n("Capabilities")) )
+{
+}
+
+CapabilitiesJob::~CapabilitiesJob()
+{
+}
+
+QStringList CapabilitiesJob::capabilities() const
+{
+ Q_D(const CapabilitiesJob);
+ return d->capabilities;
+}
+
+void CapabilitiesJob::doStart()
+{
+ Q_D(CapabilitiesJob);
+ d->tags << d->sessionInternal()->sendCommand( "CAPABILITY" );
+}
+
+void CapabilitiesJob::handleResponse( const Message &response )
+{
+
+ Q_D(CapabilitiesJob);
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 2
+ && response.content[1].toString()=="CAPABILITY" ) {
+ for (int i=2; i<response.content.size(); ++i) {
+ d->capabilities << response.content[i].toString().toUpper();
+ }
+ emit capabilitiesReceived(d->capabilities);
+ }
+ }
+}
+
diff --git a/kimap/capabilitiesjob.h b/kimap/capabilitiesjob.h
new file mode 100644
index 0000000..0306f26
--- /dev/null
+++ b/kimap/capabilitiesjob.h
@@ -0,0 +1,78 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_CAPABILITIESJOB_H
+#define KIMAP_CAPABILITIESJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class CapabilitiesJobPrivate;
+
+/**
+ * Checks server capabilities.
+ *
+ * This job can be run in any open session.
+ *
+ * This simply asks the server what capabilities it supports
+ * (using the CAPABILITY command) and returns the list
+ * provided by the server. The list may, therefore, be
+ * inaccurate: the server may claim to support something
+ * it does not implement properly, or it may omit a feature
+ * that it does, in reality, support.
+ */
+class KIMAP_EXPORT CapabilitiesJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(CapabilitiesJob)
+
+ friend class SessionPrivate;
+
+ public:
+ CapabilitiesJob( Session *session );
+ virtual ~CapabilitiesJob();
+
+ /**
+ * The capabilities the server claims to support.
+ *
+ * This will return an empty list until the job has completed.
+ */
+ QStringList capabilities() const;
+
+ Q_SIGNALS:
+ /**
+ * Notifies listeners that the capabilities have been fetched.
+ *
+ * @param capabilities The capabilities the server claims to support.
+ */
+ void capabilitiesReceived( const QStringList &capabilities );
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse( const Message &response );
+};
+
+}
+
+#endif
diff --git a/kimap/closejob.cpp b/kimap/closejob.cpp
new file mode 100644
index 0000000..d3f3d8f
--- /dev/null
+++ b/kimap/closejob.cpp
@@ -0,0 +1,54 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "closejob.h"
+
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class CloseJobPrivate : public JobPrivate
+ {
+ public:
+ CloseJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~CloseJobPrivate() { }
+ };
+}
+
+using namespace KIMAP;
+
+CloseJob::CloseJob( Session *session )
+ : Job( *new CloseJobPrivate(session, i18n("Close")) )
+{
+}
+
+CloseJob::~CloseJob()
+{
+}
+
+void CloseJob::doStart()
+{
+ Q_D(CloseJob);
+ d->tags << d->sessionInternal()->sendCommand( "CLOSE" );
+}
+
diff --git a/kimap/closejob.h b/kimap/closejob.h
new file mode 100644
index 0000000..3cadc72
--- /dev/null
+++ b/kimap/closejob.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_CLOSEJOB_H
+#define KIMAP_CLOSEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class CloseJobPrivate;
+
+/**
+ * Closes the current mailbox.
+ *
+ * This job can only be run when the session is in the selected state.
+ *
+ * Permanently removes all messages that have the \\Deleted
+ * flag set from the currently selected mailbox, and returns
+ * to the authenticated state from the selected state.
+ *
+ * The server will not provide any notifications of which
+ * messages were expunged, so this is quicker than doing
+ * an expunge and then implicitly closing the mailbox
+ * (by selecting or examining another mailbox or logging
+ * out).
+ *
+ * No messages are removed if the mailbox is open in a read-only
+ * state, or if the server supports ACLs and the user does not
+ * have the Acl::Expunge right on the mailbox.
+ */
+class KIMAP_EXPORT CloseJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(CloseJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit CloseJob( Session *session );
+ virtual ~CloseJob();
+
+ protected:
+ virtual void doStart();
+};
+
+}
+
+#endif
diff --git a/kimap/copyjob.cpp b/kimap/copyjob.cpp
new file mode 100644
index 0000000..fe4d9d8
--- /dev/null
+++ b/kimap/copyjob.cpp
@@ -0,0 +1,135 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "copyjob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+//TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response
+
+namespace KIMAP
+{
+ class CopyJobPrivate : public JobPrivate
+ {
+ public:
+ CopyJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~CopyJobPrivate() { }
+
+ QString mailBox;
+ ImapSet set;
+ bool uidBased;
+ ImapSet resultingUids;
+ };
+}
+
+using namespace KIMAP;
+
+CopyJob::CopyJob( Session *session )
+ : Job( *new CopyJobPrivate(session, i18n("Copy")) )
+{
+ Q_D(CopyJob);
+ d->uidBased = false;
+}
+
+CopyJob::~CopyJob()
+{
+}
+
+void CopyJob::setMailBox( const QString &mailBox )
+{
+ Q_D(CopyJob);
+ d->mailBox = mailBox;
+}
+
+QString CopyJob::mailBox() const
+{
+ Q_D(const CopyJob);
+ return d->mailBox;
+}
+
+void CopyJob::setSequenceSet( const ImapSet &set )
+{
+ Q_D(CopyJob);
+ d->set = set;
+}
+
+ImapSet CopyJob::sequenceSet() const
+{
+ Q_D(const CopyJob);
+ return d->set;
+}
+
+
+void CopyJob::setUidBased( bool uidBased )
+{
+ Q_D(CopyJob);
+ d->uidBased = uidBased;
+}
+
+bool CopyJob::isUidBased() const
+{
+ Q_D(const CopyJob);
+ return d->uidBased;
+}
+
+ImapSet CopyJob::resultingUids() const
+{
+ Q_D(const CopyJob);
+ return d->resultingUids;
+}
+
+void CopyJob::doStart()
+{
+ Q_D(CopyJob);
+
+ QByteArray parameters = d->set.toImapSequenceSet()+' ';
+ parameters+= '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"';
+
+ QByteArray command = "COPY";
+ if ( d->uidBased ) {
+ command = "UID "+command;
+ }
+
+ d->tags << d->sessionInternal()->sendCommand( command, parameters );
+}
+
+void CopyJob::handleResponse( const Message &response )
+{
+ Q_D(CopyJob);
+
+ for ( QList<Message::Part>::ConstIterator it = response.responseCode.begin();
+ it != response.responseCode.end(); ++it ) {
+ if ( it->toString()=="COPYUID" ) {
+ it = it + 3;
+ if ( it < response.responseCode.end() ) {
+ d->resultingUids = ImapSet::fromImapSequenceSet( it->toString() );
+ }
+ break;
+ }
+ }
+
+ handleErrorReplies( response );
+}
+
diff --git a/kimap/copyjob.h b/kimap/copyjob.h
new file mode 100644
index 0000000..5e75fdc
--- /dev/null
+++ b/kimap/copyjob.h
@@ -0,0 +1,127 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_COPYJOB_H
+#define KIMAP_COPYJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+#include "imapset.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class CopyJobPrivate;
+
+/**
+ * Copies one or more messages to another mailbox.
+ *
+ * This job can only be run when the session is in the selected state.
+ *
+ * If the server supports ACLs, the user will need the
+ * Acl::Insert right on the target mailbox.
+ * In order to preserve message flags, the user may also need
+ * some combination of Acl::DeleteMessage,
+ * Acl::KeepSeen and Acl::Write on the
+ * target mailbox.
+ */
+class KIMAP_EXPORT CopyJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(CopyJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit CopyJob( Session *session );
+ virtual ~CopyJob();
+
+ /**
+ * Sets the destination mailbox.
+ *
+ * If the mailbox does not exist, the server should not create
+ * it automatically and the job should fail. Note, however,
+ * that a conforming server may create the mailbox automatically.
+ *
+ * @param mailBox the (unquoted) name of the mailbox where the
+ * messages should be copied to
+ */
+ void setMailBox( const QString &mailBox );
+ /**
+ * The destination mailbox
+ */
+ QString mailBox() const;
+
+ /**
+ * Sets the messages to be copied
+ *
+ * If sequence numbers are given, isUidBased() should be false. If UIDs
+ * are given, isUidBased() should be true.
+ *
+ * RFC 3501 is unclear as to what should happen if invalid sequence numbers
+ * are passed. If non-existent UIDs are passed, they will be ignored.
+ *
+ * @param set the sequence numbers or UIDs of the messages to be copied
+ */
+ void setSequenceSet( const ImapSet &set );
+ /**
+ * The messages that will be copied.
+ *
+ * isUidBased() can be used to check whether the ImapSet contains
+ * sequence numbers or UIDs.
+ *
+ * @return the sequence numbers or UIDs of the messages to be copied
+ */
+ ImapSet sequenceSet() const;
+
+ /**
+ * Set how the sequence set should be interpreted.
+ *
+ * @param uidBased if @c true the argument to setSequenceSet will be
+ * interpreted as UIDs, if @c false it will be interpreted
+ * as sequence numbers
+ */
+ void setUidBased( bool uidBased );
+ /**
+ * How to interpret the sequence set.
+ *
+ * @return if @c true the result of sequenceSet() should be
+ * interpreted as UIDs, if @c false it should be interpreted
+ * as sequence numbers
+ */
+ bool isUidBased() const;
+
+ /**
+ * The UIDs of the new copies of the messages
+ *
+ * This will be an empty set if no messages have been copied yet
+ * or if the server does not support the UIDPLUS extension.
+ */
+ ImapSet resultingUids() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+};
+
+}
+
+#endif
diff --git a/kimap/createjob.cpp b/kimap/createjob.cpp
new file mode 100644
index 0000000..e26cf19
--- /dev/null
+++ b/kimap/createjob.cpp
@@ -0,0 +1,71 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "createjob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "rfccodecs.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class CreateJobPrivate : public JobPrivate
+ {
+ public:
+ CreateJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~CreateJobPrivate() { }
+
+ QString mailBox;
+ };
+}
+
+using namespace KIMAP;
+
+CreateJob::CreateJob( Session *session )
+ : Job( *new CreateJobPrivate(session, i18n("Create")) )
+{
+}
+
+CreateJob::~CreateJob()
+{
+}
+
+void CreateJob::doStart()
+{
+ Q_D(CreateJob);
+ d->tags << d->sessionInternal()->sendCommand( "CREATE", '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"' );
+}
+
+void CreateJob::setMailBox( const QString &mailBox )
+{
+ Q_D(CreateJob);
+ d->mailBox = mailBox;
+}
+
+QString CreateJob::mailBox() const
+{
+ Q_D(const CreateJob);
+ return d->mailBox;
+}
+
+
diff --git a/kimap/createjob.h b/kimap/createjob.h
new file mode 100644
index 0000000..daab478
--- /dev/null
+++ b/kimap/createjob.h
@@ -0,0 +1,76 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_CREATEJOB_H
+#define KIMAP_CREATEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class CreateJobPrivate;
+
+/**
+ * Creates a new mailbox
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * This job will fail if the mailbox already exists.
+ *
+ * If the server supports ACLs, the user must have the
+ * Acl::CreateMailbox permission on the parent
+ * mailbox. Note that what is meant by "parent mailbox"
+ * depends on the server: . and / are typical hierachy
+ * delimiters.
+ */
+class KIMAP_EXPORT CreateJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(CreateJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit CreateJob( Session *session );
+ virtual ~CreateJob();
+
+ /**
+ * Set the name of the new mailbox
+ *
+ * @param mailBox an (unquoted) identifier that does not correspond
+ * to an existing mailbox name
+ */
+ void setMailBox( const QString &mailBox );
+ /**
+ * The name of the mailbox that will be created
+ */
+ QString mailBox() const;
+
+ protected:
+ virtual void doStart();
+};
+
+}
+
+#endif
diff --git a/kimap/deleteacljob.cpp b/kimap/deleteacljob.cpp
new file mode 100644
index 0000000..7d81110
--- /dev/null
+++ b/kimap/deleteacljob.cpp
@@ -0,0 +1,72 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "deleteacljob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "acljobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class DeleteAclJobPrivate : public AclJobBasePrivate
+ {
+ public:
+ DeleteAclJobPrivate( Session *session, const QString& name ) : AclJobBasePrivate(session, name) {}
+ ~DeleteAclJobPrivate() { }
+ };
+}
+
+using namespace KIMAP;
+
+DeleteAclJob::DeleteAclJob( Session *session )
+ : AclJobBase( session )
+{
+ Q_D(DeleteAclJob);
+ d->m_name = i18n("DeleteAclJob");
+}
+
+DeleteAclJob::~DeleteAclJob()
+{
+}
+
+void DeleteAclJob::doStart()
+{
+ Q_D(DeleteAclJob);
+
+ d->tags << d->sessionInternal()->sendCommand( "DELETEACL", '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + "\" \"" + d->id);
+}
+
+void DeleteAclJob::setIdentifier( const QByteArray &identifier )
+{
+ Q_D(DeleteAclJob);
+ d->setIdentifier(identifier);
+}
+
+QByteArray DeleteAclJob::identifier()
+{
+ Q_D(DeleteAclJob);
+ return d->identifier();
+}
+
+
diff --git a/kimap/deleteacljob.h b/kimap/deleteacljob.h
new file mode 100644
index 0000000..0f06055
--- /dev/null
+++ b/kimap/deleteacljob.h
@@ -0,0 +1,74 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_DELETEACLJOB_H
+#define KIMAP_DELETEACLJOB_H
+
+#include "kimap_export.h"
+
+#include "acljobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class DeleteAclJobPrivate;
+
+/**
+ * Removes an identifier from the ACL of a mailbox.
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * The user must have the Acl::Admin permission
+ * on the mailbox for this job to succeed (see
+ * MyRightsJob).
+ *
+ * This job requires that the server supports the ACL
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc4314.html">RFC 4314</a>.
+ */
+class KIMAP_EXPORT DeleteAclJob : public AclJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(DeleteAclJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit DeleteAclJob( Session *session );
+ virtual ~DeleteAclJob();
+
+ /**
+ * Sets the identifier to remove
+ */
+ void setIdentifier( const QByteArray &identifier );
+ /**
+ * The identifier that will be removed
+ */
+ QByteArray identifier();
+
+ protected:
+ virtual void doStart();
+
+};
+
+}
+
+#endif
diff --git a/kimap/deletejob.cpp b/kimap/deletejob.cpp
new file mode 100644
index 0000000..b24ff94
--- /dev/null
+++ b/kimap/deletejob.cpp
@@ -0,0 +1,70 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "deletejob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class DeleteJobPrivate : public JobPrivate
+ {
+ public:
+ DeleteJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~DeleteJobPrivate() { }
+
+ QString mailBox;
+ };
+}
+
+using namespace KIMAP;
+
+DeleteJob::DeleteJob( Session *session )
+ : Job( *new DeleteJobPrivate(session, i18n("Delete")) )
+{
+}
+
+DeleteJob::~DeleteJob()
+{
+}
+
+void DeleteJob::doStart()
+{
+ Q_D(DeleteJob);
+ d->tags << d->sessionInternal()->sendCommand( "DELETE", '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"' );
+}
+
+void DeleteJob::setMailBox( const QString &mailBox )
+{
+ Q_D(DeleteJob);
+ d->mailBox = mailBox;
+}
+
+QString DeleteJob::mailBox() const
+{
+ Q_D(const DeleteJob);
+ return d->mailBox;
+}
+
diff --git a/kimap/deletejob.h b/kimap/deletejob.h
new file mode 100644
index 0000000..2876a90
--- /dev/null
+++ b/kimap/deletejob.h
@@ -0,0 +1,72 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_DELETEJOB_H
+#define KIMAP_DELETEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+class DeleteJobPrivate;
+
+/**
+ * Delete a mailbox
+ *
+ * Note that some servers will refuse to delete a
+ * mailbox unless it is empty (ie: all mails have
+ * had their \Deleted flag set, and then the
+ * mailbox has been expunged).
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * If the server supports ACLs, you will need the
+ * Acl::DeleteMailbox right on the mailbox.
+ */
+class KIMAP_EXPORT DeleteJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(DeleteJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit DeleteJob( Session *session );
+ virtual ~DeleteJob();
+
+ /**
+ * Set the mailbox to delete.
+ */
+ void setMailBox( const QString &mailBox );
+ /**
+ * The mailbox that will be deleted.
+ */
+ QString mailBox() const;
+
+ protected:
+ virtual void doStart();
+};
+
+}
+
+#endif
diff --git a/kimap/expungejob.cpp b/kimap/expungejob.cpp
new file mode 100644
index 0000000..af380bc
--- /dev/null
+++ b/kimap/expungejob.cpp
@@ -0,0 +1,83 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "expungejob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class ExpungeJobPrivate : public JobPrivate
+ {
+ public:
+ ExpungeJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~ExpungeJobPrivate() { }
+#if 0
+ QList< int > items;
+#endif
+ };
+}
+
+using namespace KIMAP;
+
+ExpungeJob::ExpungeJob( Session *session )
+ : Job( *new ExpungeJobPrivate(session, i18n("Expunge")) )
+{
+}
+
+ExpungeJob::~ExpungeJob()
+{
+}
+
+void ExpungeJob::doStart()
+{
+ Q_D(ExpungeJob);
+ d->tags << d->sessionInternal()->sendCommand( "EXPUNGE" );
+}
+
+void ExpungeJob::handleResponse( const Message &response )
+{
+// Q_D(ExpungeJob);
+
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 2 ) {
+ QByteArray code = response.content[2].toString();
+ if (code == "EXPUNGE") {
+#if 0
+ QByteArray s = response.content[1].toString();
+ bool ok = true;
+ int id = s.toInt(&ok);
+ if (ok) {
+ d->items.append(id);
+ }
+ //TODO error handling
+#endif
+ return;
+ }
+ }
+ kDebug() << "Unhandled response: " << response.toString().constData();
+
+ }
+}
+
diff --git a/kimap/expungejob.h b/kimap/expungejob.h
new file mode 100644
index 0000000..9862f59
--- /dev/null
+++ b/kimap/expungejob.h
@@ -0,0 +1,63 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_EXPUNGEJOB_H
+#define KIMAP_EXPUNGEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class ExpungeJobPrivate;
+
+/**
+ * Expunges the deleted messages in the selected mailbox.
+ *
+ * This permanently removes any messages that have the
+ * \Deleted flag set in the selected mailbox.
+ *
+ * This job can only be run when the session is in the
+ * selected state.
+ *
+ * If the server supports ACLs, the user will need the
+ * Acl::Expunge right on the mailbox.
+ */
+class KIMAP_EXPORT ExpungeJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(ExpungeJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit ExpungeJob( Session *session );
+ virtual ~ExpungeJob();
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+};
+
+}
+
+#endif
diff --git a/kimap/fetchjob.cpp b/kimap/fetchjob.cpp
new file mode 100644
index 0000000..d513c59
--- /dev/null
+++ b/kimap/fetchjob.cpp
@@ -0,0 +1,531 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "fetchjob.h"
+
+#include <QtCore/QTimer>
+#include <KDE/KDebug>
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class FetchJobPrivate : public JobPrivate
+ {
+ public:
+ FetchJobPrivate( FetchJob *job, Session *session, const QString& name ) : JobPrivate( session, name ), q(job), uidBased(false) { }
+ ~FetchJobPrivate() { }
+
+ void parseBodyStructure( const QByteArray &structure, int &pos, KMime::Content *content );
+ void parsePart( const QByteArray &structure, int &pos, KMime::Content *content );
+ QByteArray parseString( const QByteArray &structure, int &pos );
+ QByteArray parseSentence( const QByteArray &structure, int &pos );
+ void skipLeadingSpaces( const QByteArray &structure, int &pos );
+
+ void emitPendings()
+ {
+ if ( pendingUids.isEmpty() ) {
+ return;
+ }
+
+ if ( !pendingParts.isEmpty() ) {
+ emit q->partsReceived( selectedMailBox,
+ pendingUids, pendingParts );
+ }
+ if ( !pendingSizes.isEmpty() || !pendingFlags.isEmpty() ) {
+ emit q->headersReceived( selectedMailBox,
+ pendingUids, pendingSizes,
+ pendingFlags, pendingMessages );
+ }
+ if ( !pendingMessages.isEmpty() ) {
+ emit q->messagesReceived( selectedMailBox,
+ pendingUids, pendingMessages );
+ }
+
+ pendingUids.clear();
+ pendingMessages.clear();
+ pendingParts.clear();
+ pendingSizes.clear();
+ pendingFlags.clear();
+ }
+
+ FetchJob * const q;
+
+ ImapSet set;
+ bool uidBased;
+ FetchJob::FetchScope scope;
+ QString selectedMailBox;
+
+ QTimer emitPendingsTimer;
+ QMap<qint64, MessagePtr> pendingMessages;
+ QMap<qint64, MessageParts> pendingParts;
+ QMap<qint64, MessageFlags> pendingFlags;
+ QMap<qint64, qint64> pendingSizes;
+ QMap<qint64, qint64> pendingUids;
+ };
+}
+
+using namespace KIMAP;
+
+FetchJob::FetchJob( Session *session )
+ : Job( *new FetchJobPrivate(this, session, i18n("Fetch")) )
+{
+ Q_D(FetchJob);
+ d->scope.mode = FetchScope::Content;
+ connect( &d->emitPendingsTimer, SIGNAL(timeout()),
+ this, SLOT(emitPendings()) );
+}
+
+FetchJob::~FetchJob()
+{
+}
+
+void FetchJob::setSequenceSet( const ImapSet &set )
+{
+ Q_D(FetchJob);
+ Q_ASSERT( !set.toImapSequenceSet().trimmed().isEmpty() );
+ d->set = set;
+}
+
+ImapSet FetchJob::sequenceSet() const
+{
+ Q_D(const FetchJob);
+ return d->set;
+}
+
+void FetchJob::setUidBased(bool uidBased)
+{
+ Q_D(FetchJob);
+ d->uidBased = uidBased;
+}
+
+bool FetchJob::isUidBased() const
+{
+ Q_D(const FetchJob);
+ return d->uidBased;
+}
+
+void FetchJob::setScope( const FetchScope &scope )
+{
+ Q_D(FetchJob);
+ d->scope = scope;
+}
+
+FetchJob::FetchScope FetchJob::scope() const
+{
+ Q_D(const FetchJob);
+ return d->scope;
+}
+
+QMap<qint64, MessagePtr> FetchJob::messages() const
+{
+ return QMap<qint64, MessagePtr>();
+}
+
+QMap<qint64, MessageParts> FetchJob::parts() const
+{
+ return QMap<qint64, MessageParts>();
+}
+
+QMap<qint64, MessageFlags> FetchJob::flags() const
+{
+ return QMap<qint64, MessageFlags>();
+}
+
+QMap<qint64, qint64> FetchJob::sizes() const
+{
+ return QMap<qint64, qint64>();
+}
+
+QMap<qint64, qint64> FetchJob::uids() const
+{
+ return QMap<qint64, qint64>();
+}
+
+void FetchJob::doStart()
+{
+ Q_D(FetchJob);
+
+ QByteArray parameters = d->set.toImapSequenceSet()+' ';
+ Q_ASSERT( !parameters.trimmed().isEmpty() );
+
+ switch ( d->scope.mode ) {
+ case FetchScope::Headers:
+ if ( d->scope.parts.isEmpty() ) {
+ parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)";
+ } else {
+ parameters+='(';
+ foreach ( const QByteArray &part, d->scope.parts ) {
+ parameters+="BODY.PEEK["+part+".MIME] ";
+ }
+ parameters+="UID)";
+ }
+ break;
+ case FetchScope::Flags:
+ parameters+="(FLAGS UID)";
+ break;
+ case FetchScope::Structure:
+ parameters+="(BODYSTRUCTURE UID)";
+ break;
+ case FetchScope::Content:
+ if ( d->scope.parts.isEmpty() ) {
+ parameters+="(BODY.PEEK[] UID)";
+ } else {
+ parameters+='(';
+ foreach ( const QByteArray &part, d->scope.parts ) {
+ parameters+="BODY.PEEK["+part+"] ";
+ }
+ parameters+="UID)";
+ }
+ break;
+ case FetchScope::Full:
+ parameters+="(RFC822.SIZE INTERNALDATE BODY.PEEK[] FLAGS UID)";
+ break;
+ case FetchScope::HeaderAndContent:
+ if ( d->scope.parts.isEmpty() ) {
+ parameters+="(BODY.PEEK[] FLAGS UID)";
+ } else {
+ parameters+="(BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)]";
+ foreach ( const QByteArray &part, d->scope.parts ) {
+ parameters+=" BODY.PEEK["+part+".MIME] BODY.PEEK["+part+"]"; //krazy:exclude=doublequote_chars
+ }
+ parameters+=" FLAGS UID)";
+ }
+ break;
+ }
+
+ QByteArray command = "FETCH";
+ if ( d->uidBased ) {
+ command = "UID "+command;
+ }
+
+ d->emitPendingsTimer.start( 100 );
+ d->selectedMailBox = d->m_session->selectedMailBox();
+ d->tags << d->sessionInternal()->sendCommand( command, parameters );
+}
+
+void FetchJob::handleResponse( const Message &response )
+{
+ Q_D(FetchJob);
+
+ // We can predict it'll be handled by handleErrorReplies() so stop
+ // the timer now so that result() will really be the last emitted signal.
+ if ( !response.content.isEmpty()
+ && d->tags.size() == 1
+ && d->tags.contains( response.content.first().toString() ) ) {
+ d->emitPendingsTimer.stop();
+ d->emitPendings();
+ }
+
+ if (handleErrorReplies(response) == NotHandled ) {
+ if ( response.content.size() == 4
+ && response.content[2].toString()=="FETCH"
+ && response.content[3].type()==Message::Part::List ) {
+
+ qint64 id = response.content[1].toString().toLongLong();
+ QList<QByteArray> content = response.content[3].toList();
+
+ MessagePtr message( new KMime::Message );
+ bool shouldParseMessage = false;
+ MessageParts parts;
+
+ for ( QList<QByteArray>::ConstIterator it = content.constBegin();
+ it!=content.constEnd(); ++it ) {
+ QByteArray str = *it;
+ ++it;
+
+ if ( it==content.constEnd() ) { // Uh oh, message was truncated?
+ kWarning() << "FETCH reply got truncated, skipping.";
+ break;
+ }
+
+ if ( str=="UID" ) {
+ d->pendingUids[id] = it->toLongLong();
+ } else if ( str=="RFC822.SIZE" ) {
+ d->pendingSizes[id] = it->toLongLong();
+ } else if ( str=="INTERNALDATE" ) {
+ message->date()->setDateTime( KDateTime::fromString( *it, KDateTime::RFCDate ) );
+ } else if ( str=="FLAGS" ) {
+ if ( (*it).startsWith('(') && (*it).endsWith(')') ) {
+ QByteArray str = *it;
+ str.chop(1);
+ str.remove(0, 1);
+ d->pendingFlags[id] = str.split(' ');
+ } else {
+ d->pendingFlags[id] << *it;
+ }
+ } else if ( str=="BODYSTRUCTURE" ) {
+ int pos = 0;
+ d->parseBodyStructure(*it, pos, message.get());
+ message->assemble();
+ d->pendingMessages[id] = message;
+ } else if ( str.startsWith( "BODY[") ) { //krazy:exclude=strings
+ if ( !str.endsWith(']') ) { // BODY[ ... ] might have been split, skip until we find the ]
+ while ( !(*it).endsWith(']') ) ++it;
+ ++it;
+ }
+
+ int index;
+ if ( (index=str.indexOf("HEADER"))>0 || (index=str.indexOf("MIME"))>0 ) { // headers
+ if ( str[index-1]=='.' ) {
+ QByteArray partId = str.mid( 5, index-6 );
+ if ( !parts.contains( partId ) ) {
+ parts[partId] = ContentPtr( new KMime::Content );
+ }
+ parts[partId]->setHead(*it);
+ parts[partId]->parse();
+ d->pendingParts[id] = parts;
+ } else {
+ message->setHead(*it);
+ shouldParseMessage = true;
+ }
+ } else { // full payload
+ if ( str=="BODY[]" ) {
+ message->setContent( KMime::CRLFtoLF(*it) );
+ shouldParseMessage = true;
+
+ d->pendingMessages[id] = message;
+ } else {
+ QByteArray partId = str.mid( 5, str.size()-6 );
+ if ( !parts.contains( partId ) ) {
+ parts[partId] = ContentPtr( new KMime::Content );
+ }
+ parts[partId]->setBody(*it);
+ parts[partId]->parse();
+
+ d->pendingParts[id] = parts;
+ }
+ }
+ }
+ }
+
+ if ( shouldParseMessage ) {
+ message->parse();
+ }
+
+ // For the headers mode the message is built in several
+ // steps, hence why we wait it to be done until putting it
+ // in the pending queue.
+ if ( d->scope.mode == FetchScope::Headers || d->scope.mode == FetchScope::HeaderAndContent ) {
+ d->pendingMessages[id] = message;
+ }
+ }
+ }
+}
+
+void FetchJobPrivate::parseBodyStructure(const QByteArray &structure, int &pos, KMime::Content *content)
+{
+ skipLeadingSpaces(structure, pos);
+
+ if ( structure[pos]!='(' ) {
+ return;
+ }
+
+ pos++;
+
+
+ if ( structure[pos]!='(' ) { // simple part
+ pos--;
+ parsePart( structure, pos, content );
+ } else { // multi part
+ content->contentType()->setMimeType("MULTIPART/MIXED");
+ while ( pos<structure.size() && structure[pos]=='(' ) {
+ KMime::Content *child = new KMime::Content;
+ content->addContent( child );
+ parseBodyStructure( structure, pos, child );
+ child->assemble();
+ }
+
+ QByteArray subType = parseString( structure, pos );
+ content->contentType()->setMimeType( "MULTIPART/"+subType );
+
+ QByteArray parameters = parseSentence( structure, pos ); // FIXME: Read the charset
+ if (parameters.contains("BOUNDARY") ) {
+ content->contentType()->setBoundary(parameters.remove(0, parameters.indexOf("BOUNDARY") + 11).split('\"')[0]);
+ }
+
+ QByteArray disposition = parseSentence( structure, pos );
+ if ( disposition.contains("INLINE") ) {
+ content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
+ } else if ( disposition.contains("ATTACHMENT") ) {
+ content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
+ }
+
+ parseSentence( structure, pos ); // Ditch the body language
+ }
+
+ // Consume what's left
+ while ( pos<structure.size() && structure[pos]!=')' ) {
+ skipLeadingSpaces( structure, pos );
+ parseSentence( structure, pos );
+ skipLeadingSpaces( structure, pos );
+ }
+
+ pos++;
+}
+
+void FetchJobPrivate::parsePart( const QByteArray &structure, int &pos, KMime::Content *content )
+{
+ if ( structure[pos]!='(' ) {
+ return;
+ }
+
+ pos++;
+
+ QByteArray mainType = parseString( structure, pos );
+ QByteArray subType = parseString( structure, pos );
+
+ content->contentType()->setMimeType( mainType+'/'+subType );
+
+ parseSentence( structure, pos ); // Ditch the parameters... FIXME: Read it to get charset and name
+ parseString( structure, pos ); // ... and the id
+
+ content->contentDescription()->from7BitString( parseString( structure, pos ) );
+
+ parseString( structure, pos ); // Ditch the encoding too
+ parseString( structure, pos ); // ... and the size
+ parseString( structure, pos ); // ... and the line count
+
+ QByteArray disposition = parseSentence( structure, pos );
+ if ( disposition.contains("INLINE") ) {
+ content->contentDisposition()->setDisposition( KMime::Headers::CDinline );
+ } else if ( disposition.contains("ATTACHMENT") ) {
+ content->contentDisposition()->setDisposition( KMime::Headers::CDattachment );
+ }
+ if ( (content->contentDisposition()->disposition() == KMime::Headers::CDattachment
+ || content->contentDisposition()->disposition() == KMime::Headers::CDinline)
+ && disposition.contains("FILENAME") ) {
+ QByteArray filename = disposition.remove(0, disposition.indexOf("FILENAME") + 11).split('\"')[0];
+ content->contentDisposition()->setFilename( filename );
+ }
+
+ // Consume what's left
+ while ( pos<structure.size() && structure[pos]!=')' ) {
+ skipLeadingSpaces( structure, pos );
+ parseSentence( structure, pos );
+ skipLeadingSpaces( structure, pos );
+ }
+}
+
+QByteArray FetchJobPrivate::parseSentence( const QByteArray &structure, int &pos )
+{
+ QByteArray result;
+ int stack = 0;
+
+ skipLeadingSpaces( structure, pos );
+
+ if ( structure[pos]!='(' ) {
+ return parseString( structure, pos );
+ }
+
+ int start = pos;
+
+ do {
+ switch ( structure[pos] ) {
+ case '(':
+ pos++;
+ stack++;
+ break;
+ case ')':
+ pos++;
+ stack--;
+ break;
+ case '[':
+ pos++;
+ stack++;
+ break;
+ case ']':
+ pos++;
+ stack--;
+ break;
+ default:
+ skipLeadingSpaces(structure, pos);
+ parseString(structure, pos);
+ skipLeadingSpaces(structure, pos);
+ break;
+ }
+ } while ( pos<structure.size() && stack!=0 );
+
+ result = structure.mid( start, pos - start );
+
+ return result;
+}
+
+QByteArray FetchJobPrivate::parseString( const QByteArray &structure, int &pos )
+{
+ QByteArray result;
+
+ skipLeadingSpaces( structure, pos );
+
+ int start = pos;
+ bool foundSlash = false;
+
+ // quoted string
+ if ( structure[pos] == '"' ) {
+ pos++;
+ Q_FOREVER {
+ if ( structure[pos] == '\\' ) {
+ pos+= 2;
+ foundSlash = true;
+ continue;
+ }
+ if ( structure[pos] == '"' ) {
+ result = structure.mid( start+1, pos - start - 1);
+ pos++;
+ break;
+ }
+ pos++;
+ }
+ } else { // unquoted string
+ Q_FOREVER {
+ if ( structure[pos] == ' ' || structure[pos] == '(' || structure[pos] == ')' || structure[pos] == '[' || structure[pos] == ']' || structure[pos] == '\n' || structure[pos] == '\r' || structure[pos] == '"') {
+ break;
+ }
+ if (structure[pos] == '\\')
+ foundSlash = true;
+ pos++;
+ }
+
+ result = structure.mid( start, pos - start );
+
+ // transform unquoted NIL
+ if ( result == "NIL" )
+ result.clear();
+ }
+
+ // simplify slashes
+ if ( foundSlash ) {
+ while ( result.contains( "\\\"" ) )
+ result.replace( "\\\"", "\"" );
+ while ( result.contains( "\\\\" ) )
+ result.replace( "\\\\", "\\" );
+ }
+
+ return result;
+}
+
+void FetchJobPrivate::skipLeadingSpaces( const QByteArray &structure, int &pos )
+{
+ while ( structure[pos]==' ' && pos<structure.size() ) pos++;
+}
+
+#include "moc_fetchjob.cpp"
diff --git a/kimap/fetchjob.h b/kimap/fetchjob.h
new file mode 100644
index 0000000..aa31d2d
--- /dev/null
+++ b/kimap/fetchjob.h
@@ -0,0 +1,303 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_FETCHJOB_H
+#define KIMAP_FETCHJOB_H
+
+#include "kimap_export.h"
+
+#include "imapset.h"
+#include "job.h"
+
+#include "kmime/kmime_content.h"
+#include "kmime/kmime_message.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class FetchJobPrivate;
+
+typedef boost::shared_ptr<KMime::Content> ContentPtr;
+typedef QMap<QByteArray, ContentPtr> MessageParts;
+
+typedef boost::shared_ptr<KMime::Message> MessagePtr;
+typedef QList<QByteArray> MessageFlags;
+
+/**
+ * Fetch message data from the server
+ *
+ * All data is returned using the signals, so you need to connect to
+ * the relevant signal (or all of them) before starting the job.
+ *
+ * This job will always use BODY.PEEK rather than BODY to fetch message
+ * content, so it will not set the \Seen flag.
+ *
+ * This job can only be run when the session is in the selected state.
+ */
+class KIMAP_EXPORT FetchJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(FetchJob)
+
+ friend class SessionPrivate;
+
+ public:
+ /**
+ * Used to indicate what message data should be fetched.
+ *
+ * This doesn't provide the same fine-grained control over
+ * what is fetched that the IMAP FETCH command normally
+ * does, but the common cases are catered for.
+ */
+ struct FetchScope
+ {
+ /**
+ * Used to indicate what part of the message should be fetched.
+ */
+ enum Mode {
+ /**
+ * Fetch RFC-2822 or MIME message headers.
+ *
+ * To fetch MIME headers for a MIME part, populate the @p parts field.
+ *
+ * If the RFC-2822 headers are requested (so @p parts is empty), the
+ * returned information is:
+ * - To, From, Message-id, References In-Reply-To, Subject and Date headers
+ * - The message size (in octets)
+ * - The internal date of the message
+ * - The message flags
+ * - The message UID
+ */
+ Headers,
+ /**
+ * Fetch the message flags (the UID is also fetched)
+ */
+ Flags,
+ /**
+ * Fetch the MIME message body structure (the UID is also fetched)
+ */
+ Structure,
+ /**
+ * Fetch the message content (the UID is also fetched)
+ *
+ * To fetch only certain MIME parts (see Structure), populate the
+ * @p parts field.
+ */
+ Content,
+ /**
+ * Fetch the complete message.
+ */
+ Full,
+ /**
+ * Fetch the message MIME headers and the content of parts specified in the @p parts
+ * field.
+ *
+ * If @p parts is empty, this mode will return the full message, just like
+ * FetchScope::Content
+ *
+ * Use case:
+ * -# Start a FetchJob with the FetchScope::Structure mode to retrieve the structure
+ * of the message.
+ * -# Parse the structure to identify the parts that are interesting (ie: probably
+ * everything but attachments).
+ * -# Start another FetchJob with FetchScope::HeaderAndContent to fetch those parts.
+ * -# At the request of the user, you can repeat the step above to fetch the attachments.
+ *
+ * @since 4.7
+ */
+ HeaderAndContent
+ };
+
+ /**
+ * Specify which message parts to operate on.
+ *
+ * This refers to multipart-MIME message parts or MIME-IMB encapsulated
+ * message parts.
+ *
+ * Note that this is ignored unless @p mode is Headers or Content.
+ *
+ * If @p mode is Headers, this sets the parts to get the MIME headers
+ * for. If this list is empty, the headers for the whole message
+ * (the RFC-2822 headers) are fetched.
+ *
+ * If @p mode is Content, this sets the parts to fetch. Parts are
+ * fetched wholesale. If this list is empty, the whole message body
+ * is fetched (all MIME parts together).
+ */
+ QList<QByteArray> parts;
+ /**
+ * Specify what message data should be fetched.
+ */
+ Mode mode;
+ };
+
+ explicit FetchJob( Session *session );
+ virtual ~FetchJob();
+
+ /**
+ * Set which messages to fetch data for.
+ *
+ * If sequence numbers are given, isUidBased() should be false. If UIDs
+ * are given, isUidBased() should be true.
+ *
+ * @param set the sequence numbers or UIDs of the messages to fetch data for
+ */
+ void setSequenceSet( const ImapSet &set );
+ /**
+ * The messages that will be fetched.
+ */
+ ImapSet sequenceSet() const;
+
+ /**
+ * Set how the sequence set should be interpreted.
+ *
+ * @param uidBased if @c true the argument to setSequenceSet will be
+ * interpreted as UIDs, if @c false it will be interpreted
+ * as sequence numbers
+ */
+ void setUidBased(bool uidBased);
+ /**
+ * How to interpret the sequence set.
+ *
+ * @return if @c true the result of sequenceSet() should be
+ * interpreted as UIDs, if @c false it should be interpreted
+ * as sequence numbers
+ */
+ bool isUidBased() const;
+
+ /**
+ * Sets what data should be fetched.
+ *
+ * The default scope is FetchScope::Content (all content parts).
+ *
+ * @param scope a FetchScope object describing what data
+ * should be fetched
+ */
+ void setScope( const FetchScope &scope );
+ /**
+ * Specifies what data will be fetched.
+ */
+ FetchScope scope() const;
+
+ // XXX: [alexmerry, 2010-07-24]: BIC? Behaviour change
+ /** @deprecated returns an empty map; use the signals instead */
+ KDE_DEPRECATED QMap<qint64, MessagePtr> messages() const;
+ /** @deprecated returns an empty map; use the signals instead */
+ KDE_DEPRECATED QMap<qint64, MessageParts> parts() const;
+ /** @deprecated returns an empty map; use the signals instead */
+ KDE_DEPRECATED QMap<qint64, MessageFlags> flags() const;
+ /** @deprecated returns an empty map; use the signals instead */
+ KDE_DEPRECATED QMap<qint64, qint64> sizes() const;
+ /** @deprecated returns an empty map; use the signals instead */
+ KDE_DEPRECATED QMap<qint64, qint64> uids() const;
+
+ Q_SIGNALS:
+ /**
+ * Provides header and message results.
+ *
+ * This signal will be emitted if the requested scope mode
+ * was FetchScope::Full, FetchScope::Flags or
+ * FetchScope::Headers with no parts specified
+ *
+ * This signal may be emitted any number of times before
+ * the result() signal is emitted. The result() signal will
+ * only be emitted once all results have been reported via
+ * one of the signals.
+ *
+ * Note that, depending on the scope, some of the parameters
+ * of this signal may be empty maps.
+ *
+ * @param mailBox the name of the mailbox the fetch job was
+ * executed on
+ * @param uids a map from message sequence numbers to message UIDs;
+ * this will always be populated
+ * @param sizes a map from message sequence numbers to message sizes
+ * (sizes are in octets and refer to the transfer encoding of
+ * the message); populated if the scope is FetchScope::Full or
+ * FetchScope::Headers
+ * @param flags a map from message sequence numbers to message flags;
+ * populated if the scope is FetchScope::Flags, FetchScope::Full
+ * of FetchScope::Headers
+ * @param messages a map from message sequence numbers to message contents (including
+ * headers); populated if the scope is FetchScope::Full,
+ * FetchScope::Headers or FetchScope::Structure
+ */
+ void headersReceived( const QString &mailBox,
+ const QMap<qint64, qint64> &uids,
+ const QMap<qint64, qint64> &sizes,
+ const QMap<qint64, KIMAP::MessageFlags> &flags,
+ const QMap<qint64, KIMAP::MessagePtr> &messages );
+
+ /**
+ * Provides header and message results.
+ *
+ * This signal will be emitted if the requested scope mode
+ * was FetchScope::Content or FetchScope::Headers with no
+ * parts specified or FetchScope::Structure.
+ *
+ * This signal may be emitted any number of times before
+ * the result() signal is emitted. The result() signal will
+ * only be emitted once all results have been reported via
+ * one of the signals.
+ *
+ *
+ * @param mailBox the name of the mailbox the fetch job was
+ * executed on
+ * @param uids a map from message sequence numbers to message UIDs
+ * @param messages a map from message sequence numbers to message contents
+ */
+ void messagesReceived( const QString &mailBox,
+ const QMap<qint64, qint64> &uids,
+ const QMap<qint64, KIMAP::MessagePtr> &messages );
+
+ /**
+ * Provides header and message results.
+ *
+ * This signal will be emitted if the requested scope mode
+ * was FetchScope::Content or FetchScope::Headers with
+ * specified parts.
+ *
+ * This signal may be emitted any number of times before
+ * the result() signal is emitted. The result() signal will
+ * only be emitted once all results have been reported via
+ * one of the signals.
+ *
+ * @param mailBox the name of the mailbox the fetch job was
+ * executed on
+ * @param uids a map from message sequence numbers to message UIDs
+ * @param parts a map from message sequence numbers to message part collections
+ */
+ void partsReceived( const QString &mailBox,
+ const QMap<qint64, qint64> &uids,
+ const QMap<qint64, KIMAP::MessageParts> &parts );
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+ private:
+ Q_PRIVATE_SLOT( d_func(), void emitPendings() )
+};
+
+}
+
+#endif
diff --git a/kimap/getacljob.cpp b/kimap/getacljob.cpp
new file mode 100644
index 0000000..9f5d19a
--- /dev/null
+++ b/kimap/getacljob.cpp
@@ -0,0 +1,113 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "getacljob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "acljobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class GetAclJobPrivate : public AclJobBasePrivate
+ {
+ public:
+ GetAclJobPrivate( Session *session, const QString& name ) : AclJobBasePrivate(session, name) {}
+ ~GetAclJobPrivate() { }
+
+ QMap<QByteArray, Acl::Rights> userRights;
+ };
+}
+
+using namespace KIMAP;
+
+GetAclJob::GetAclJob( Session *session )
+ : AclJobBase( *new GetAclJobPrivate(session, i18n("GetAcl") ))
+{
+}
+
+GetAclJob::~GetAclJob()
+{
+}
+
+void GetAclJob::doStart()
+{
+ Q_D(GetAclJob);
+
+ d->tags << d->sessionInternal()->sendCommand( "GETACL", '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + '\"');
+}
+
+void GetAclJob::handleResponse( const Message &response )
+{
+ Q_D(GetAclJob);
+// kDebug() << response.toString();
+
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 4
+ && response.content[1].toString() == "ACL" ) {
+ int i = 3;
+ while ( i < response.content.size() - 1 ) {
+ QByteArray id = response.content[i].toString();
+ QByteArray rights = response.content[i + 1].toString();
+ d->userRights[id] = Acl::rightsFromString(rights);
+ i += 2;
+ }
+ }
+ }
+}
+
+QList<QByteArray> GetAclJob::identifiers() const
+{
+ Q_D(const GetAclJob);
+ return d->userRights.keys();
+}
+
+bool GetAclJob::hasRightEnabled(const QByteArray &identifier, Acl::Right right) const
+{
+ Q_D(const GetAclJob);
+ if (d->userRights.contains(identifier))
+ {
+ Acl::Rights rights = d->userRights[identifier];
+ return rights & right;
+ }
+
+ return false;
+}
+
+Acl::Rights GetAclJob::rights(const QByteArray &identifier) const
+{
+ Q_D(const GetAclJob);
+ Acl::Rights result;
+ if (d->userRights.contains(identifier))
+ {
+ result = d->userRights[identifier];
+ }
+ return result;
+}
+
+QMap<QByteArray, Acl::Rights> GetAclJob::allRights() const
+{
+ Q_D(const GetAclJob);
+ return d->userRights;
+}
+
diff --git a/kimap/getacljob.h b/kimap/getacljob.h
new file mode 100644
index 0000000..dd652d2
--- /dev/null
+++ b/kimap/getacljob.h
@@ -0,0 +1,141 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_GETACLJOB_H
+#define KIMAP_GETACLJOB_H
+
+#include "kimap_export.h"
+
+#include "acljobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class GetAclJobPrivate;
+
+
+/**
+ * Gets the ACL for a mailbox
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * The user must have the Acl::Admin permission
+ * on the mailbox for this job to succeed (see
+ * MyRightsJob).
+ *
+ * This job requires that the server supports the ACL
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc4314.html">RFC 4314</a>.
+ *
+ * The meaning of identifiers depends on the server implementation,
+ * with the following restrictions:
+ *
+ * - "anyone" means any authenticated user, including anonymous
+ * - an identifier starting with a minus sign ('-') indicates
+ * "negative rights": rights that should be taken away from
+ * matching users
+ *
+ * Other than the above restrictions, ACL identifiers are usually
+ * IMAP usernames, but could potentially be group names as well.
+ *
+ * Note that negative rights override positive rights: if
+ * "fred" and "-fred" are both assigned the 'w' right, the
+ * user "fred" will not have the 'w' right.
+ */
+class KIMAP_EXPORT GetAclJob : public AclJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(GetAclJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit GetAclJob( Session *session );
+ virtual ~GetAclJob();
+
+ /**
+ * The identifiers present in the ACL.
+ *
+ * This method will return an empty list if the job has
+ * not yet been run.
+ *
+ * See the GetAclJob documentation for an explanation of
+ * identifiers; in particular, identifiers starting with
+ * '-' specify negative rights.
+ */
+ QList<QByteArray> identifiers() const;
+ /**
+ * Check whether an identifier has a given right set
+ *
+ * The result of this method is undefined if the job has
+ * not yet completed.
+ *
+ * See the GetAclJob documentation for an explanation of
+ * identifiers; in particular, identifiers starting with
+ * '-' specify negative rights.
+ *
+ * Note that this will not tell you whether the net result
+ * of all the ACL entries means that a given user has
+ * a certain right.
+ *
+ * @param identifier the identifier to check the rights for
+ * @param right the right to check for
+ */
+ bool hasRightEnabled(const QByteArray &identifier, Acl::Right right) const;
+ /**
+ * Get the rights associated with an identifier.
+ *
+ * The result of this method is undefined if the job has
+ * not yet completed.
+ *
+ * See the GetAclJob documentation for an explanation of
+ * identifiers; in particular, identifiers starting with
+ * '-' specify negative rights.
+ *
+ * Note that this will not tell you the rights that a
+ * given user will have once all the ACL entries have
+ * been taken into account.
+ *
+ * @param identifier the identifier to check the rights for
+ */
+ Acl::Rights rights(const QByteArray &identifier) const;
+
+ /**
+ * Gets the full access control list.
+ *
+ * The result of this method is undefined if the job has
+ * not yet completed.
+ *
+ * See the GetAclJob documentation for an explanation of
+ * identifiers; in particular, identifiers starting with
+ * '-' specify negative rights.
+ */
+ QMap<QByteArray, Acl::Rights> allRights() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+};
+
+}
+
+#endif
diff --git a/kimap/getmetadatajob.cpp b/kimap/getmetadatajob.cpp
new file mode 100644
index 0000000..2507469
--- /dev/null
+++ b/kimap/getmetadatajob.cpp
@@ -0,0 +1,211 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "getmetadatajob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "metadatajobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class GetMetaDataJobPrivate : public MetaDataJobBasePrivate
+ {
+ public:
+ GetMetaDataJobPrivate( Session *session, const QString& name ) : MetaDataJobBasePrivate(session, name), maxSize(-1), depth("0") { }
+ ~GetMetaDataJobPrivate() { }
+
+ qint64 maxSize;
+ QByteArray depth;
+ QList<QByteArray> entries;
+ QList<QByteArray> attributes;
+ QMap<QString, QMap<QByteArray, QMap<QByteArray, QByteArray> > > metadata;
+ // ^ mailbox ^ entry ^attribute ^ value
+ };
+}
+
+using namespace KIMAP;
+
+GetMetaDataJob::GetMetaDataJob( Session *session )
+ : MetaDataJobBase( *new GetMetaDataJobPrivate(session, i18n("GetMetaData")) )
+{
+}
+
+GetMetaDataJob::~GetMetaDataJob()
+{
+}
+
+void GetMetaDataJob::doStart()
+{
+ Q_D(GetMetaDataJob);
+ QByteArray parameters;
+ parameters = '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + "\" ";
+
+ QByteArray command = "GETMETADATA";
+ if (d->serverCapability == Annotatemore) {
+ d->m_name = i18n("GetAnnotation");
+ command = "GETANNOTATION";
+ if (d->entries.size() > 1)
+ parameters += '(';
+ Q_FOREACH(const QByteArray &entry, d->entries) {
+ parameters += '\"' + entry + "\" ";
+ }
+ if (d->entries.size() > 1)
+ parameters[parameters.length() -1 ] = ')';
+ else
+ parameters.truncate(parameters.length() -1);
+
+ parameters += ' ';
+
+ if (d->attributes.size() > 1)
+ parameters += '(';
+ Q_FOREACH(const QByteArray &attribute, d->attributes) {
+ parameters += '\"' + attribute + "\" ";
+ }
+ if (d->attributes.size() > 1)
+ parameters[parameters.length() -1 ] = ')';
+ else
+ parameters.truncate(parameters.length() -1);
+
+ } else {
+ if (d->depth != "0") {
+ parameters += "(DEPTH " + d->depth;
+ }
+ if (d->maxSize != -1) {
+ parameters += "(MAXSIZE " + QByteArray::number(d->maxSize) + ')';
+ }
+ if (d->depth != "0") {
+ parameters += " )";
+ }
+
+ if (d->entries.size() > 1)
+ parameters += '(';
+ Q_FOREACH(const QByteArray &entry, d->entries) {
+ parameters += '\"' + entry + "\" ";
+ }
+ if (d->entries.size() > 1)
+ parameters[parameters.length() -1 ] = ')';
+ }
+
+ if (d->entries.isEmpty()) {
+ parameters += ')';
+ }
+
+ d->tags << d->sessionInternal()->sendCommand( command, parameters );
+// kDebug() << "SENT: " << command << " " << parameters;
+}
+
+void GetMetaDataJob::handleResponse( const Message &response )
+{
+ Q_D(GetMetaDataJob);
+// kDebug() << "GOT: " << response.toString();
+
+ //TODO: handle NO error messages having [METADATA MAXSIZE NNN], [METADATA TOOMANY], [METADATA NOPRIVATE] (see rfc5464)
+ // or [ANNOTATEMORE TOOBIG], [ANNOTATEMORE TOOMANY] respectively
+ if (handleErrorReplies(response) == NotHandled ) {
+ if ( response.content.size() >= 4 ) {
+ if (d->serverCapability == Annotatemore && response.content[1].toString() == "ANNOTATION" ) {
+ QString mailBox = QString::fromUtf8( KIMAP::decodeImapFolderName( response.content[2].toString() ) );
+
+ int i = 3;
+ while (i < response.content.size() - 1) {
+ QByteArray entry = response.content[i].toString();
+ QList<QByteArray> attributes = response.content[i + 1].toList();
+ int j = 0;
+ while ( j < attributes.size() - 1) {
+ d->metadata[mailBox][entry][attributes[j]] = attributes[j + 1];
+ j += 2;
+ }
+ i += 2;
+ }
+ } else
+ if (d->serverCapability == Metadata && response.content[1].toString() == "METADATA" ) {
+ QString mailBox = QString::fromUtf8( KIMAP::decodeImapFolderName( response.content[2].toString() ) );
+
+ QList<QByteArray> entries = response.content[3].toList();
+ int i = 0;
+ while ( i < entries.size() - 1) {
+ d->metadata[mailBox][entries[i]][""] = entries[i + 1];
+ i += 2;
+ }
+ }
+ }
+ }
+}
+
+void GetMetaDataJob::addEntry(const QByteArray &entry, const QByteArray &attribute)
+{
+ Q_D(GetMetaDataJob);
+ if (d->serverCapability == Annotatemore && attribute.isNull())
+ qWarning() << "In ANNOTATEMORE mode an attribute must be specified with addEntry!";
+ d->entries.append(entry);
+ d->attributes.append(attribute);
+}
+
+void GetMetaDataJob::setMaximumSize(qint64 size)
+{
+ Q_D(GetMetaDataJob);
+ d->maxSize = size;
+}
+
+void GetMetaDataJob::setDepth(Depth depth)
+{
+ Q_D(GetMetaDataJob);
+
+ switch (depth)
+ {
+ case OneLevel:
+ d->depth = "1"; //krazy:exclude=doublequote_chars
+ break;
+ case AllLevels:
+ d->depth = "infinity";
+ break;
+ default:
+ d->depth = "0"; //krazy:exclude=doublequote_chars
+ }
+}
+
+QByteArray GetMetaDataJob::metaData(const QString &mailBox, const QByteArray &entry, const QByteArray &attribute) const
+{
+ Q_D(const GetMetaDataJob);
+ QByteArray attr = attribute;
+
+ if (d->serverCapability == Metadata)
+ attr = "";
+
+ QByteArray result;
+ if (d->metadata.contains(mailBox)) {
+ if (d->metadata[mailBox].contains(entry)) {
+ result = d->metadata[mailBox][entry].value(attr);
+ }
+ }
+
+ return result;
+}
+
+QMap<QByteArray, QMap<QByteArray, QByteArray> > GetMetaDataJob::allMetaData(const QString &mailBox) const
+{
+ Q_D(const GetMetaDataJob);
+ return d->metadata[mailBox];
+}
+
diff --git a/kimap/getmetadatajob.h b/kimap/getmetadatajob.h
new file mode 100644
index 0000000..0ba43eb
--- /dev/null
+++ b/kimap/getmetadatajob.h
@@ -0,0 +1,196 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_GETMETADATAJOB_H
+#define KIMAP_GETMETADATAJOB_H
+
+#include "kimap_export.h"
+
+#include "metadatajobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class GetMetaDataJobPrivate;
+
+/**
+ * Fetches mailbox metadata.
+ *
+ * Provides support for the IMAP METADATA extension; both the
+ * final RFC version
+ * (<a href="http://tools.ietf.org/html/rfc5464">RFC 5464</a>)
+ * and the older, incompatible draft version (known as ANNOTATEMORE)
+ * (<a
+ * href="http://tools.ietf.org/html/draft-daboo-imap-annotatemore-07"
+ * >draft-daboo-imap-annotatemore-07</a>). See setServerCompatibility().
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * If the server supports ACLs, the user will need the
+ * Acl::Lookup right on the mailbox, as well as one of
+ * - Acl::Read
+ * - Acl::KeepSeen
+ * - Acl::Write
+ * - Acl::Insert
+ * - Acl::Post
+ * Otherwise, the user must be able to list the mailbox
+ * and either read or write the message content.
+ *
+ * Note also that on servers that implement the Annotatemore
+ * version of the extension, only Acl::Lookup rights are
+ * required (ie: the user must be able to list the mailbox).
+ */
+class KIMAP_EXPORT GetMetaDataJob : public MetaDataJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE( GetMetaDataJob )
+
+ friend class SessionPrivate;
+
+ public:
+ explicit GetMetaDataJob( Session *session );
+ virtual ~GetMetaDataJob();
+
+ /**
+ * Used to specify the depth of the metadata heirachy to walk.
+ */
+ enum Depth {
+ NoDepth = 0, /**< Only the requested entries */
+ OneLevel, /**< The requested entries and all their direct children */
+ AllLevels /**< The requested entries and all their descendants */
+ };
+
+ Q_DECLARE_FLAGS( Depths, Depth )
+
+ /**
+ * Add an entry to the query list.
+ *
+ * See SetMetaDataJob for a description of metadata entry names.
+ *
+ * When operating in Annotatemore mode, you should provide an attribute
+ * name. Typically this will be "value", "value.priv" or "value.shared",
+ * although you might want to fetch the "content-type" or
+ * "content-language" attributes as well.
+ *
+ * @param entry the metadata entry name
+ * @param attribute the attribute name, in Annotatemore mode
+ */
+ void addEntry( const QByteArray &entry, const QByteArray &attribute = QByteArray() );
+
+ /**
+ * Limits the size of returned metadata entries.
+ *
+ * In order to save time or bandwidth, it is possible to prevent the
+ * server from returning metadata entries that are larger than a
+ * certain size. These entries will simply not appear in the
+ * list returned by allMetaData(), and will not be accessible using
+ * metaData().
+ *
+ * Note that this is only used when the server capability mode is
+ * Metadata.
+ *
+ * The default is no limit (-1). A value of less than -1 will cause
+ * the job to fail.
+ *
+ * @param size the entry size limit, in octets, or -1 for no limit
+ */
+ void setMaximumSize( qint64 size );
+
+ /**
+ * Sets whether to retrieve children or descendants of the requested entries.
+ *
+ * Metadata entry names are heirachical, much like UNIX path names.
+ * It therefore makes sense to ask for an entry and all its children
+ * (OneLevel) or an entry and all its descendants (AllLevels).
+ *
+ * For example, /shared/foo/bar/baz is a child of /shared/foo/bar and a
+ * descendent of /shared/foo. So if you request the entry "/shared/foo"
+ * with depth NoDepth, you will only get the "/shared/foo" entry. If
+ * you set the depth to OneLevel, you will also get "/shared/foo/bar".
+ * If you set the depth to AllLevels, you will also get
+ * "/shared/foo/bar/baz", and every other metadata entry that starts
+ * with "/shared/foo/".
+ *
+ * Note that this is only used when the server capability mode is
+ * Metadata.
+ *
+ * @param depth the depth of the metadata tree to return
+ */
+ void setDepth( Depth depth );
+
+ /**
+ * Get a single metadata entry.
+ *
+ * The metadata must have been requested using addEntry(), and
+ * the job must have completed successfully, or this method
+ * will not return anything.
+ *
+ * Note that if setMaximumSize() was used to limit the size of
+ * returned metadata, this method may return an empty QByteArray
+ * even if the metadata entry was requested and exists on the
+ * server. This will happen when the metadata entry is larger
+ * than the size limit given to setMaximumSize().
+ *
+ * @param mailBox the mailbox the metadata is attached to, or
+ * an empty string for server metadata
+ * @param entry the entry to get
+ * @param attribute (only in Annotatemore mode) the attribute to get
+ * @return the metadata entry value
+ */
+ // XXX: what's with the mailBox argument in a class that has setMailBox()?
+ // KJobs are not intended to be run more than once
+ QByteArray metaData( const QString &mailBox, const QByteArray &entry,
+ const QByteArray &attribute = QByteArray() ) const;
+
+ /**
+ * Get all the metadata for a given mailbox.
+ *
+ * The returned map is from metadata entry names to attributes or values.
+ *
+ * If operating in Metadata mode, the metadata value is stored against the
+ * empty QByteArray:
+ * @code
+ * map = job.allMetaData( "INBOX" );
+ * QByteArray value = map[ "/shared/comment" ].value( QByteArray() );
+ * @endcode
+ *
+ * The equivalent in Annotatemore mode would be:
+ * @code
+ * map = job.allMetaData( "INBOX" );
+ * QByteArray value = map[ "/comment" ].value( "value.shared" );
+ * @endcode
+ *
+ * @param mailBox a mailbox name or an empty string for server metadata
+ * @return a map from metadata entry names to attributes or values
+ */
+ // XXX: what's with the mailBox argument in a class that has setMailBox()?
+ // KJobs are not intended to be run more than once
+ QMap<QByteArray, QMap<QByteArray, QByteArray> > allMetaData( const QString &mailBox ) const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse( const Message &response );
+
+};
+
+}
+
+#endif
diff --git a/kimap/getquotajob.cpp b/kimap/getquotajob.cpp
new file mode 100644
index 0000000..3f6e5e1
--- /dev/null
+++ b/kimap/getquotajob.cpp
@@ -0,0 +1,83 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "getquotajob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "quotajobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class GetQuotaJobPrivate : public QuotaJobBasePrivate
+ {
+ public:
+ GetQuotaJobPrivate( Session *session, const QString& name ) : QuotaJobBasePrivate(session, name) { }
+ ~GetQuotaJobPrivate() { }
+
+ QByteArray root;
+ };
+}
+
+using namespace KIMAP;
+
+GetQuotaJob::GetQuotaJob( Session *session )
+ : QuotaJobBase( *new GetQuotaJobPrivate(session, i18n("GetQuota")) )
+{
+}
+
+GetQuotaJob::~GetQuotaJob()
+{
+}
+
+void GetQuotaJob::doStart()
+{
+ Q_D(GetQuotaJob);
+ //XXX: [alexmerry, 2010-07-24]: should d->root be quoted properly?
+ d->tags << d->sessionInternal()->sendCommand( "GETQUOTA", '\"' + d->root + '\"');
+}
+
+void GetQuotaJob::handleResponse(const Message &response)
+{
+ Q_D(GetQuotaJob);
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 4
+ && response.content[1].toString() == "QUOTA" ) {
+ d->quota = d->readQuota(response.content[3]);
+ }
+ }
+}
+
+void GetQuotaJob::setRoot(const QByteArray& root)
+{
+ Q_D(GetQuotaJob);
+
+ d->root = root;
+}
+
+QByteArray GetQuotaJob::root() const
+{
+ Q_D(const GetQuotaJob);
+
+ return d->root;
+}
+
diff --git a/kimap/getquotajob.h b/kimap/getquotajob.h
new file mode 100644
index 0000000..01231ea
--- /dev/null
+++ b/kimap/getquotajob.h
@@ -0,0 +1,80 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_GETQUOTAJOB_H
+#define KIMAP_GETQUOTAJOB_H
+
+#include "quotajobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class GetQuotaJobPrivate;
+
+/**
+ * Gets resource limits for a quota root.
+ *
+ * Quotas are defined with respect to "resources" and "quota roots".
+ * A resource is a numerical property that can be limited, such
+ * as the octet size of all the messages in a mailbox, or the
+ * number of messages in a mailbox. Each mailbox has one or more
+ * quota roots, which are where the resource limits are defined.
+ * A quota root may or may not be a mailbox name, and an empty
+ * string is a valid quota root. All mailboxes with the same quota
+ * root share the resource limits of the quota root.
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * This job requires that the server supports the QUOTA
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc2087.html">RFC 2087</a>.
+ */
+class KIMAP_EXPORT GetQuotaJob : public QuotaJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(GetQuotaJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit GetQuotaJob( Session *session );
+ virtual ~GetQuotaJob();
+
+ /**
+ * Set the quota root to get the resource limits for.
+ *
+ * @see GetQuotaRootJob
+ */
+ void setRoot(const QByteArray &root);
+ /**
+ * The quota root that resource limit information will be fetched for.
+ */
+ QByteArray root() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+};
+
+}
+
+#endif
diff --git a/kimap/getquotarootjob.cpp b/kimap/getquotarootjob.cpp
new file mode 100644
index 0000000..484c200
--- /dev/null
+++ b/kimap/getquotarootjob.cpp
@@ -0,0 +1,185 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "getquotarootjob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "quotajobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class GetQuotaRootJobPrivate : public QuotaJobBasePrivate
+ {
+ public:
+ GetQuotaRootJobPrivate( Session *session, const QString& name ) : QuotaJobBasePrivate(session, name) { }
+ ~GetQuotaRootJobPrivate() { }
+
+ QString mailBox;
+ QList<QByteArray> rootList;
+ QMap< QByteArray, QMap<QByteArray, QPair<qint64, qint64> > > quotas;
+ };
+}
+
+using namespace KIMAP;
+
+GetQuotaRootJob::GetQuotaRootJob( Session *session )
+ : QuotaJobBase( *new GetQuotaRootJobPrivate(session, i18n("GetQuotaRoot")) )
+{
+}
+
+GetQuotaRootJob::~GetQuotaRootJob()
+{
+}
+
+void GetQuotaRootJob::doStart()
+{
+ Q_D(GetQuotaRootJob);
+ d->tags << d->sessionInternal()->sendCommand( "GETQUOTAROOT", '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + '\"');
+}
+
+void GetQuotaRootJob::handleResponse(const Message &response)
+{
+ Q_D(GetQuotaRootJob);
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 3 ) {
+ if (response.content[1].toString() == "QUOTAROOT" ) {
+ d->rootList.clear();
+ //some impls don't give the root a name which for us seems as if
+ //there were no message part
+ if ( response.content.size() == 3 ) {
+ d->rootList.append("");
+ } else {
+ int i = 3;
+ while ( i < response.content.size())
+ {
+ d->rootList.append(response.content[i].toString());
+ i++;
+ }
+ }
+ } else
+ if (response.content[1].toString() == "QUOTA" ) {
+ QByteArray rootName;
+ int quotaContentIndex = 3;
+ //some impls don't give the root a name in the response
+ if (response.content.size() == 3 ) {
+ quotaContentIndex = 2;
+ } else {
+ rootName = response.content[2].toString();
+ }
+
+ const QMap<QByteArray, QPair<qint64, qint64> >& quota = d->readQuota(response.content[quotaContentIndex]);
+ if (d->quotas.contains(rootName)) {
+ d->quotas[ rootName ].unite(quota);
+ } else {
+ d->quotas[ rootName ] = quota;
+ }
+ }
+ }
+ }
+}
+
+
+void GetQuotaRootJob::setMailBox(const QString& mailBox)
+{
+ Q_D(GetQuotaRootJob);
+
+ d->mailBox = mailBox;
+}
+
+QString GetQuotaRootJob::mailBox() const
+{
+ Q_D(const GetQuotaRootJob);
+
+ return d->mailBox;
+}
+
+QList<QByteArray> GetQuotaRootJob::roots() const
+{
+ Q_D(const GetQuotaRootJob);
+
+ return d->rootList;
+}
+
+qint64 GetQuotaRootJob::usage(const QByteArray &root, const QByteArray &resource) const
+{
+ Q_D(const GetQuotaRootJob);
+
+ QByteArray r = resource.toUpper();
+
+ if (d->quotas.contains(root) && d->quotas[root].contains(r)) {
+ return d->quotas[root][r].first;
+ }
+
+ return -1;
+}
+
+qint64 GetQuotaRootJob::limit(const QByteArray &root, const QByteArray &resource) const
+{
+ Q_D(const GetQuotaRootJob);
+
+ QByteArray r = resource.toUpper();
+
+ if (d->quotas.contains(root) && d->quotas[root].contains(r)) {
+ return d->quotas[root][r].second;
+ }
+
+ return -1;
+}
+
+QMap<QByteArray, qint64> GetQuotaRootJob::allUsages(const QByteArray &root) const
+{
+ Q_D(const GetQuotaRootJob);
+
+ QMap<QByteArray, qint64> result;
+
+ if (d->quotas.contains(root)) {
+ const QMap< QByteArray, QPair<qint64, qint64> > quota = d->quotas[root];
+ QMapIterator<QByteArray, QPair<qint64, qint64> > it( quota );
+ while ( it.hasNext() ) {
+ it.next();
+ result[it.key()] = it.value().first;
+ }
+ }
+
+ return result;
+}
+
+QMap<QByteArray, qint64> GetQuotaRootJob::allLimits(const QByteArray &root) const
+{
+ Q_D(const GetQuotaRootJob);
+
+ QMap<QByteArray, qint64> result;
+
+ if (d->quotas.contains(root)) {
+ const QMap< QByteArray, QPair<qint64, qint64> > quota = d->quotas[root];
+ QMapIterator<QByteArray, QPair<qint64, qint64> > it( quota );
+ while ( it.hasNext() ) {
+ it.next();
+ result[it.key()] = it.value().second;
+ }
+ }
+
+ return result;
+}
+
diff --git a/kimap/getquotarootjob.h b/kimap/getquotarootjob.h
new file mode 100644
index 0000000..b1fa49e
--- /dev/null
+++ b/kimap/getquotarootjob.h
@@ -0,0 +1,124 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_GETQUOTAROOTJOB_H
+#define KIMAP_GETQUOTAROOTJOB_H
+
+#include "quotajobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class GetQuotaRootJobPrivate;
+
+/**
+ * Gets the quota root and resource limits for a mailbox.
+ *
+ * Quotas are defined with respect to "resources" and "quota roots".
+ * A resource is a numerical property that can be limited, such
+ * as the octet size of all the messages in a mailbox, or the
+ * number of messages in a mailbox. Each mailbox has one or more
+ * quota roots, which are where the resource limits are defined.
+ * A quota root may or may not be a mailbox name, and an empty
+ * string is a valid quota root. All mailboxes with the same quota
+ * root share the resource limits of the quota root.
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * This job requires that the server supports the QUOTA
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc2087.html">RFC 2087</a>.
+ */
+class KIMAP_EXPORT GetQuotaRootJob : public QuotaJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(GetQuotaRootJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit GetQuotaRootJob( Session *session );
+ virtual ~GetQuotaRootJob();
+
+ /**
+ * Set the mailbox to get the quota roots for.
+ *
+ * @param mailBox the name of an existing mailbox
+ */
+ void setMailBox(const QString &mailBox);
+ /**
+ * The mailbox that the quota roots will be fetched for.
+ */
+ QString mailBox() const;
+
+ /**
+ * The quota roots for the mailbox.
+ */
+ QList<QByteArray> roots() const;
+ /**
+ * Get the current usage for a resource.
+ *
+ * Note that if there is no limit for a resource, the
+ * server will not provide information about resource
+ * usage.
+ *
+ * @param root the quota root to get the resource usage for
+ * @param resource the resource to get the usage for
+ * @return the resource usage in appropriate units, or -1
+ * if the usage is unknown or there is no
+ * limit on the resource
+ */
+ qint64 usage(const QByteArray &root, const QByteArray &resource) const;
+ /**
+ * Get the current limit for a resource.
+ *
+ * @param root the quota root to get the resource limit for
+ * @param resource the resource to get the limit for
+ * @return the resource limit in appropriate units, or -1
+ * if the limit is unknown or there is no
+ * limit on the resource
+ */
+ qint64 limit(const QByteArray &root, const QByteArray &resource) const;
+
+ /**
+ * Get a map containing all resource usage figures for a quota root.
+ *
+ * @param root the quota root to get resource usage figures for
+ * @return a map from resource names to usage figures
+ */
+ QMap<QByteArray, qint64> allUsages(const QByteArray &root) const;
+ /**
+ * Get a map containing all resource limits for a quota root.
+ *
+ * @param root the quota root to get resource limits for
+ * @return a map from resource names to limits
+ */
+ QMap<QByteArray, qint64> allLimits(const QByteArray &root) const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+};
+
+}
+
+#endif
diff --git a/kimap/idlejob.cpp b/kimap/idlejob.cpp
new file mode 100644
index 0000000..3fcb91e
--- /dev/null
+++ b/kimap/idlejob.cpp
@@ -0,0 +1,160 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "idlejob.h"
+
+#include <QtCore/QTimer>
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class IdleJobPrivate : public JobPrivate
+ {
+ public:
+ IdleJobPrivate( IdleJob *job, Session *session, const QString& name )
+ : JobPrivate( session, name ), q(job),
+ messageCount( -1 ), recentCount( -1 ),
+ lastMessageCount( -1 ), lastRecentCount( -1 ),
+ originalSocketTimeout( -1 ) { }
+ ~IdleJobPrivate() { }
+
+ void emitStats()
+ {
+ emitStatsTimer.stop();
+
+ emit q->mailBoxStats(q, m_session->selectedMailBox(),
+ messageCount, recentCount);
+
+ lastMessageCount = messageCount;
+ lastRecentCount = recentCount;
+
+ messageCount = -1;
+ recentCount = -1;
+ }
+
+ IdleJob * const q;
+
+ QTimer emitStatsTimer;
+
+ int messageCount;
+ int recentCount;
+
+ int lastMessageCount;
+ int lastRecentCount;
+
+ int originalSocketTimeout;
+ };
+}
+
+using namespace KIMAP;
+
+IdleJob::IdleJob( Session *session )
+ : Job( *new IdleJobPrivate(this, session, i18nc("name of the idle job", "Idle")) )
+{
+ Q_D(IdleJob);
+ connect( &d->emitStatsTimer, SIGNAL(timeout()),
+ this, SLOT(emitStats()) );
+}
+
+IdleJob::~IdleJob()
+{
+}
+
+void KIMAP::IdleJob::stop()
+{
+ Q_D(IdleJob);
+ d->sessionInternal()->setSocketTimeout( d->originalSocketTimeout );
+ d->sessionInternal()->sendData( "DONE" );
+}
+
+void IdleJob::doStart()
+{
+ Q_D(IdleJob);
+ d->originalSocketTimeout = d->sessionInternal()->socketTimeout();
+ d->sessionInternal()->setSocketTimeout( -1 );
+ d->tags << d->sessionInternal()->sendCommand( "IDLE" );
+}
+
+void IdleJob::handleResponse( const Message &response )
+{
+ Q_D(IdleJob);
+
+ // We can predict it'll be handled by handleErrorReplies() so emit
+ // pending signals now (if needed) so that result() will really be
+ // the last emitted signal.
+ if ( !response.content.isEmpty()
+ && d->tags.size() == 1
+ && d->tags.contains( response.content.first().toString() )
+ && ( d->messageCount>=0 || d->recentCount>=0 ) ) {
+ d->emitStats();
+ }
+
+
+ if (handleErrorReplies(response) == NotHandled ) {
+ if ( response.content.size() > 0 && response.content[0].toString()=="+" ) {
+ // Got the continuation all is fine
+ return;
+
+ } else if ( response.content.size() > 2 ) {
+ if ( response.content[2].toString()=="EXISTS" ) {
+ if ( d->messageCount>=0 ) {
+ d->emitStats();
+ }
+
+ d->messageCount = response.content[1].toString().toInt();
+ } else if ( response.content[2].toString()=="RECENT" ) {
+ if ( d->recentCount>=0 ) {
+ d->emitStats();
+ }
+
+ d->recentCount = response.content[1].toString().toInt();
+ }
+ }
+
+ if ( d->messageCount>=0 && d->recentCount>=0 ) {
+ d->emitStats();
+ } else if ( d->messageCount>=0 || d->recentCount>=0 ) {
+ d->emitStatsTimer.start( 200 );
+ }
+ }
+}
+
+QString KIMAP::IdleJob::lastMailBox() const
+{
+ Q_D(const IdleJob);
+ return d->m_session->selectedMailBox();
+}
+
+int KIMAP::IdleJob::lastMessageCount() const
+{
+ Q_D(const IdleJob);
+ return d->lastMessageCount;
+}
+
+int KIMAP::IdleJob::lastRecentCount() const
+{
+ Q_D(const IdleJob);
+ return d->lastRecentCount;
+}
+
+#include "moc_idlejob.cpp"
diff --git a/kimap/idlejob.h b/kimap/idlejob.h
new file mode 100644
index 0000000..3a0917b
--- /dev/null
+++ b/kimap/idlejob.h
@@ -0,0 +1,132 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_IDLEJOB_H
+#define KIMAP_IDLEJOB_H
+
+#include "kimap_export.h"
+
+#include "imapset.h"
+#include "job.h"
+
+#include "kmime/kmime_content.h"
+#include "kmime/kmime_message.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class IdleJobPrivate;
+
+/**
+ * Idles the connection to the IMAP server.
+ *
+ * This job can be run while the client has no other use
+ * for the connection, and the server will send updates
+ * about the selected mailbox.
+ *
+ * Note that although the server may send a variety of
+ * responses while the job is running (including EXPUNGE,
+ * for example), only RECENT and EXISTS responses are
+ * actually reported by this job.
+ *
+ * The job also processes updates in pairs - if the server
+ * sends an EXISTS update but not a RECENT one (because
+ * another client is changing the mailbox contents), this
+ * job will not report the update.
+ *
+ * It only makes sense to run this job when the session is
+ * in the selected state.
+ *
+ * This job requires that the server supports the IDLE
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc2177.html">RFC 2177</a>.
+ */
+class KIMAP_EXPORT IdleJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(IdleJob)
+
+ public:
+ explicit IdleJob( Session *session );
+ virtual ~IdleJob();
+
+ /**
+ * The last mailbox status that was reported.
+ *
+ * This is just the session's selected mailbox.
+ */
+ QString lastMailBox() const;
+ /**
+ * The last message count that was reported.
+ *
+ * The server will send updates about the number of
+ * messages in the mailbox when that number changes.
+ * This is the last number it reported.
+ *
+ * @return the last message count the server reported,
+ * or -1 if it has not reported a message count
+ * since the job started.
+ */
+ int lastMessageCount() const;
+ /**
+ * The last recent message count that was reported.
+ *
+ * The server will send updates about the number of
+ * messages in the mailbox that are tagged with \Recent
+ * when that number changes. This is the last number it
+ * reported.
+ *
+ * @return the last recent message count the server reported,
+ * or -1 if it has not reported a recent message count
+ * since the job started.
+ */
+ int lastRecentCount() const;
+
+ public Q_SLOTS:
+ /**
+ * Stops the idle job.
+ */
+ void stop();
+
+ Q_SIGNALS:
+ /**
+ * Signals that the server has notified that the total and
+ * recent message counts have changed.
+ *
+ * @param job this object
+ * @param mailBox the selected mailbox
+ * @param messageCount the new total message count reported by the server
+ * @param recentCount the new "recent message" count reported by the server
+ */
+ void mailBoxStats(KIMAP::IdleJob *job, const QString &mailBox, int messageCount, int recentCount);
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+ private:
+ Q_PRIVATE_SLOT( d_func(), void emitStats() )
+};
+
+}
+
+#endif
diff --git a/kimap/imapset.cpp b/kimap/imapset.cpp
new file mode 100644
index 0000000..8588452
--- /dev/null
+++ b/kimap/imapset.cpp
@@ -0,0 +1,322 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "imapset.h"
+
+#include <QtCore/QSharedData>
+
+using namespace KIMAP;
+
+class ImapInterval::Private : public QSharedData
+{
+ public:
+ Private() :
+ QSharedData(),
+ begin( 0 ),
+ end( 0 )
+ {}
+
+ Private( const Private &other ) :
+ QSharedData( other )
+ {
+ begin = other.begin;
+ end = other.end;
+ }
+
+ Id begin;
+ Id end;
+};
+
+class ImapSet::Private : public QSharedData
+{
+ public:
+ Private() : QSharedData() {}
+ Private( const Private &other ) :
+ QSharedData( other )
+ {
+ intervals = other.intervals;
+ }
+
+ ImapInterval::List intervals;
+};
+
+
+ImapInterval::ImapInterval() :
+ d( new Private )
+{
+}
+
+ImapInterval::ImapInterval(const ImapInterval & other) :
+ d( other.d )
+{
+}
+
+ImapInterval::ImapInterval(Id begin, Id end) :
+ d( new Private )
+{
+ d->begin = begin;
+ d->end = end;
+}
+
+ImapInterval::~ ImapInterval()
+{
+}
+
+ImapInterval& ImapInterval::operator =(const ImapInterval & other)
+{
+ if ( this != & other )
+ d = other.d;
+ return *this;
+}
+
+bool ImapInterval::operator ==(const ImapInterval & other) const
+{
+ return ( d->begin == other.d->begin && d->end == other.d->end );
+}
+
+ImapInterval::Id ImapInterval::size() const
+{
+ if ( !d->begin && !d->end )
+ return 0;
+ if ( d->begin && !d->end )
+ return Q_INT64_C( 0x7FFFFFFFFFFFFFFF ) - d->begin + 1;
+ return d->end - d->begin + 1;
+}
+
+bool ImapInterval::hasDefinedBegin() const
+{
+ return d->begin != 0;
+}
+
+ImapInterval::Id ImapInterval::begin() const
+{
+ return d->begin;
+}
+
+bool ImapInterval::hasDefinedEnd() const
+{
+ return d->end != 0;
+}
+
+ImapInterval::Id ImapInterval::end() const
+{
+ if ( hasDefinedEnd() )
+ return d->end;
+ return 0xFFFFFFFF; // should be INT_MAX, but where is that defined again?
+}
+
+void ImapInterval::setBegin(Id value)
+{
+ Q_ASSERT( value >= 0 );
+ Q_ASSERT( value <= d->end || !hasDefinedEnd() );
+ d->begin = value;
+}
+
+void ImapInterval::setEnd(Id value)
+{
+ Q_ASSERT( value >= 0 );
+ Q_ASSERT( value >= d->begin || !hasDefinedBegin() );
+ d->end = value;
+}
+
+QByteArray ImapInterval::toImapSequence() const
+{
+ if ( size() == 0 )
+ return QByteArray();
+ if ( size() == 1 )
+ return QByteArray::number( d->begin );
+ QByteArray rv;
+ rv += QByteArray::number( d->begin ) + ':';
+ if ( hasDefinedEnd() )
+ rv += QByteArray::number( d->end );
+ else
+ rv += '*';
+ return rv;
+}
+
+ImapInterval ImapInterval::fromImapSequence( const QByteArray &sequence )
+{
+ QList<QByteArray> values = sequence.split( ':' );
+ if ( values.isEmpty() || values.size() > 2 ) {
+ return ImapInterval();
+ }
+
+ bool ok = false;
+ Id begin = values[0].toLongLong(&ok);
+
+ if ( !ok ) {
+ return ImapInterval();
+ }
+
+ Id end;
+
+ if ( values.size() == 1 ) {
+ end = begin;
+ } else if ( values[1] == QByteArray( "*" ) ) {
+ end = 0;
+ } else {
+ ok = false;
+ end = values[1].toLongLong(&ok);
+ if ( !ok ) {
+ return ImapInterval();
+ }
+ }
+
+ return ImapInterval( begin, end );
+}
+
+ImapSet::ImapSet() :
+ d( new Private )
+{
+}
+
+ImapSet::ImapSet( Id begin, Id end ) :
+ d( new Private )
+{
+ add( ImapInterval( begin, end ) );
+}
+
+ImapSet::ImapSet( Id value ) :
+ d( new Private )
+{
+ add( QList<Id>() << value );
+}
+
+ImapSet::ImapSet(const ImapSet & other) :
+ d( other.d )
+{
+}
+
+ImapSet::~ImapSet()
+{
+}
+
+ImapSet & ImapSet::operator =(const ImapSet & other)
+{
+ if ( this != &other )
+ d = other.d;
+ return *this;
+}
+
+bool ImapSet::operator ==(const ImapSet &other) const
+{
+ if ( d->intervals.size()!=other.d->intervals.size() ) {
+ return false;
+ }
+
+ foreach ( const ImapInterval &interval, d->intervals ) {
+ if ( !other.d->intervals.contains( interval ) ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ImapSet::add( Id value )
+{
+ add( QList<Id>() << value );
+}
+
+void ImapSet::add(const QList<Id> & values)
+{
+ QList<Id> vals = values;
+ qSort( vals );
+ for( int i = 0; i < vals.count(); ++i ) {
+ const int begin = vals[i];
+ Q_ASSERT( begin >= 0 );
+ if ( i == vals.count() - 1 ) {
+ d->intervals << ImapInterval( begin, begin );
+ break;
+ }
+ do {
+ ++i;
+ Q_ASSERT( vals[i] >= 0 );
+ if ( vals[i] != (vals[i - 1] + 1) ) {
+ --i;
+ break;
+ }
+ } while ( i < vals.count() - 1 );
+ d->intervals << ImapInterval( begin, vals[i] );
+ }
+}
+
+void ImapSet::add(const ImapInterval & interval)
+{
+ d->intervals << interval;
+}
+
+QByteArray ImapSet::toImapSequenceSet() const
+{
+ QList<QByteArray> rv;
+ foreach ( const ImapInterval &interval, d->intervals ) {
+ rv << interval.toImapSequence();
+ }
+
+ QByteArray result;
+
+ if ( !rv.isEmpty() ) {
+ result = rv.first();
+ QList<QByteArray>::ConstIterator it = rv.constBegin();
+ ++it;
+ for ( ; it != rv.constEnd(); ++it ) {
+ result += ',' + (*it);
+ }
+ }
+
+ return result;
+}
+
+ImapSet ImapSet::fromImapSequenceSet( const QByteArray &sequence )
+{
+ ImapSet result;
+
+ QList<QByteArray> intervals = sequence.split( ',' );
+
+ foreach( const QByteArray &interval, intervals ) {
+ if ( !interval.isEmpty() ) {
+ result.add( ImapInterval::fromImapSequence( interval ) );
+ }
+ }
+
+ return result;
+}
+
+ImapInterval::List ImapSet::intervals() const
+{
+ return d->intervals;
+}
+
+bool ImapSet::isEmpty() const
+{
+ return d->intervals.isEmpty();
+}
+
+QDebug& operator<<( QDebug &d, const ImapInterval &interval )
+{
+ d << interval.toImapSequence();
+ return d;
+}
+
+QDebug& operator<<( QDebug &d, const ImapSet &set )
+{
+ d << set.toImapSequenceSet();
+ return d;
+}
+
diff --git a/kimap/imapset.h b/kimap/imapset.h
new file mode 100644
index 0000000..a3417ac
--- /dev/null
+++ b/kimap/imapset.h
@@ -0,0 +1,239 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_IMAPSET_H
+#define KIMAP_IMAPSET_H
+
+#include "kimap_export.h"
+
+#include <QtCore/QByteArray>
+#include <QtCore/QDebug>
+#include <QtCore/QList>
+#include <QtCore/QMetaType>
+#include <QtCore/QSharedDataPointer>
+
+namespace KIMAP {
+
+/**
+ Represents a single interval in an ImapSet.
+ This class is implicitly shared.
+*/
+class KIMAP_EXPORT ImapInterval
+{
+ public:
+ /**
+ * Describes the ids stored in the interval.
+ */
+ typedef qint64 Id;
+
+ /**
+ A list of ImapInterval objects.
+ */
+ typedef QList<ImapInterval> List;
+
+ /**
+ Constructs an interval that covers all positive numbers.
+ */
+ ImapInterval();
+
+ /**
+ Copy constructor.
+ */
+ ImapInterval( const ImapInterval &other );
+
+ /**
+ Create a new interval.
+ @param begin The begin of the interval.
+ @param end Keep default (0) to just set the interval begin
+ */
+ explicit ImapInterval( Id begin, Id end = 0 );
+
+ /**
+ Destructor.
+ */
+ ~ImapInterval();
+
+ /**
+ Assignment operator.
+ */
+ ImapInterval& operator=( const ImapInterval &other );
+
+ /**
+ Comparison operator.
+ */
+ bool operator==( const ImapInterval &other ) const;
+
+ /**
+ Returns the size of this interval.
+ Size is only defined for finite intervals.
+ */
+ Id size() const;
+
+ /**
+ Returns true if this interval has a defined begin.
+ */
+ bool hasDefinedBegin() const;
+
+ /**
+ Returns the begin of this interval. The value is the smallest value part of the interval.
+ Only valid if begin is defined.
+ */
+ Id begin() const;
+
+ /**
+ Returns true if this intercal has been defined.
+ */
+ bool hasDefinedEnd() const;
+
+ /**
+ Returns the end of this interval. This value is the largest value part of the interval.
+ Only valid if hasDefinedEnd() returned true.
+ */
+ Id end() const;
+
+ /**
+ Sets the begin of the interval.
+ */
+ void setBegin( Id value );
+
+ /**
+ Sets the end of this interval.
+ */
+ void setEnd( Id value );
+
+ /**
+ Converts this set into an IMAP compatible sequence.
+ */
+ QByteArray toImapSequence() const;
+
+ /**
+ Return the interval corresponding to the given IMAP-compatible QByteArray representation
+ */
+ static ImapInterval fromImapSequence( const QByteArray &sequence );
+
+ private:
+ class Private;
+ QSharedDataPointer<Private> d;
+};
+
+/**
+ Represents a set of natural numbers (1->\f$\infty\f$) in a as compact as possible form.
+ Used to address Akonadi items via the IMAP protocol or in the database.
+ This class is implicitly shared.
+*/
+class KIMAP_EXPORT ImapSet
+{
+ public:
+ /**
+ * Describes the ids stored in the set.
+ */
+ typedef qint64 Id;
+
+ /**
+ Constructs an empty set.
+ */
+ ImapSet();
+
+ /**
+ Constructs a set containing a single interval.
+ */
+ ImapSet( Id begin, Id end );
+
+ /**
+ Constructs a set containing a single value.
+ */
+ explicit ImapSet( Id value );
+
+ /**
+ Copy constructor.
+ */
+ ImapSet( const ImapSet &other );
+
+ /**
+ Destructor.
+ */
+ ~ImapSet();
+
+ /**
+ Assignment operator.
+ */
+ ImapSet& operator=( const ImapSet &other );
+
+ /**
+ Comparison operator.
+ */
+ bool operator==( const ImapSet &other ) const;
+
+ /**
+ Adds a single positive integer numbers to the set.
+ The list is sorted and split into as large as possible intervals.
+ No interval merging is performed.
+ @param value A positive integer number
+ */
+ void add( Id value );
+
+ /**
+ Adds the given list of positive integer numbers to the set.
+ The list is sorted and split into as large as possible intervals.
+ No interval merging is performed.
+ @param values List of positive integer numbers in arbitrary order
+ */
+ void add( const QList<Id> &values );
+
+ /**
+ Adds the given ImapInterval to this set.
+ No interval merging is performed.
+ */
+ void add( const ImapInterval &interval );
+
+ /**
+ Returns a IMAP-compatible QByteArray representation of this set.
+ */
+ QByteArray toImapSequenceSet() const;
+
+ /**
+ Return the set corresponding to the given IMAP-compatible QByteArray representation
+ */
+ static ImapSet fromImapSequenceSet( const QByteArray &sequence );
+
+ /**
+ Returns the intervals this set consists of.
+ */
+ ImapInterval::List intervals() const;
+
+ /**
+ Returns true if this set doesn't contains any values.
+ */
+ bool isEmpty() const;
+
+ private:
+ class Private;
+ QSharedDataPointer<Private> d;
+};
+
+}
+
+KIMAP_EXPORT QDebug& operator<<( QDebug& d, const KIMAP::ImapInterval &interval );
+KIMAP_EXPORT QDebug& operator<<( QDebug& d, const KIMAP::ImapSet &set );
+
+Q_DECLARE_METATYPE( KIMAP::ImapInterval )
+Q_DECLARE_METATYPE( KIMAP::ImapInterval::List )
+Q_DECLARE_METATYPE( KIMAP::ImapSet )
+
+#endif
diff --git a/kimap/imapstreamparser.cpp b/kimap/imapstreamparser.cpp
new file mode 100644
index 0000000..50a8552
--- /dev/null
+++ b/kimap/imapstreamparser.cpp
@@ -0,0 +1,540 @@
+/*
+ Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "imapstreamparser.h"
+
+#include <ctype.h>
+#include <QIODevice>
+
+using namespace KIMAP;
+
+ImapStreamParser::ImapStreamParser( QIODevice *socket, bool serverModeEnabled )
+{
+ m_socket = socket;
+ m_isServerModeEnabled = serverModeEnabled;
+ m_position = 0;
+ m_literalSize = 0;
+}
+
+ImapStreamParser::~ImapStreamParser()
+{
+}
+
+QString ImapStreamParser::readUtf8String()
+{
+ QByteArray tmp;
+ tmp = readString();
+ QString result = QString::fromUtf8( tmp );
+ return result;
+}
+
+
+QByteArray ImapStreamParser::readString()
+{
+ QByteArray result;
+ if ( !waitForMoreData( m_data.length() == 0 ) )
+ throw ImapParserException("Unable to read more data");
+ stripLeadingSpaces();
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+
+ // literal string
+ // TODO: error handling
+ if ( hasLiteral() ) {
+ while (!atLiteralEnd()) {
+ result += readLiteralPart();
+ }
+ return result;
+ }
+
+ // quoted string
+ return parseQuotedString();
+}
+
+bool ImapStreamParser::hasString()
+{
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ int savedPos = m_position;
+ stripLeadingSpaces();
+ int pos = m_position;
+ m_position = savedPos;
+ if ( m_data.at(pos) == '{' )
+ return true; //literal string
+ if (m_data.at(pos) == '"' )
+ return true; //quoted string
+ if ( m_data.at(pos) != ' ' &&
+ m_data.at(pos) != '(' &&
+ m_data.at(pos) != ')' &&
+ m_data.at(pos) != '[' &&
+ m_data.at(pos) != ']' &&
+ m_data.at(pos) != '\n' &&
+ m_data.at(pos) != '\r' )
+ return true; //unquoted string
+
+ return false; //something else, not a string
+}
+
+bool ImapStreamParser::hasLiteral()
+{
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ int savedPos = m_position;
+ stripLeadingSpaces();
+ if ( m_data.at(m_position) == '{' )
+ {
+ int end = -1;
+ do {
+ end = m_data.indexOf( '}', m_position );
+ if ( !waitForMoreData( end == -1 ) )
+ throw ImapParserException("Unable to read more data");
+ } while (end == -1);
+ Q_ASSERT( end > m_position );
+ m_literalSize = m_data.mid( m_position + 1, end - m_position - 1 ).toInt();
+ // strip CRLF
+ m_position = end + 1;
+
+ if ( m_position < m_data.length() && m_data.at(m_position) == '\r' )
+ ++m_position;
+ if ( m_position < m_data.length() && m_data.at(m_position) == '\n' )
+ ++m_position;
+
+ //FIXME: Makes sense only on the server side?
+ if (m_isServerModeEnabled && m_literalSize > 0)
+ sendContinuationResponse( m_literalSize );
+ return true;
+ } else
+ {
+ m_position = savedPos;
+ return false;
+ }
+}
+
+bool ImapStreamParser::atLiteralEnd() const
+{
+ return (m_literalSize == 0);
+}
+
+QByteArray ImapStreamParser::readLiteralPart()
+{
+ static qint64 maxLiteralPartSize = 4096;
+ int size = qMin(maxLiteralPartSize, m_literalSize);
+
+ if ( !waitForMoreData( m_data.length() < m_position + size ) )
+ throw ImapParserException("Unable to read more data");
+
+ if ( m_data.length() < m_position + size ) { // Still not enough data
+ // Take what's already there
+ size = m_data.length() - m_position;
+ }
+
+ QByteArray result = m_data.mid(m_position, size);
+ m_position += size;
+ m_literalSize -= size;
+ Q_ASSERT(m_literalSize >= 0);
+ trimBuffer();
+
+ return result;
+}
+
+bool ImapStreamParser::hasList()
+{
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ int savedPos = m_position;
+ stripLeadingSpaces();
+ int pos = m_position;
+ m_position = savedPos;
+ if ( m_data.at(pos) == '(' )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool ImapStreamParser::atListEnd()
+{
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ int savedPos = m_position;
+ stripLeadingSpaces();
+ int pos = m_position;
+ m_position = savedPos;
+ if ( m_data.at(pos) == ')' )
+ {
+ m_position = pos + 1;
+ return true;
+ }
+
+ return false;
+}
+
+QList<QByteArray> ImapStreamParser::readParenthesizedList()
+{
+ QList<QByteArray> result;
+ if (! waitForMoreData( m_data.length() <= m_position ) )
+ throw ImapParserException("Unable to read more data");
+
+ stripLeadingSpaces();
+ if ( m_data.at(m_position) != '(' )
+ return result; //no list found
+
+ bool concatToLast = false;
+ int count = 0;
+ int sublistbegin = m_position;
+ int i = m_position + 1;
+ Q_FOREVER {
+ if ( !waitForMoreData( m_data.length() <= i ) )
+ {
+ m_position = i;
+ throw ImapParserException("Unable to read more data");
+ }
+ if ( m_data.at(i) == '(' ) {
+ ++count;
+ if ( count == 1 )
+ sublistbegin = i;
+ ++i;
+ continue;
+ }
+ if ( m_data.at(i) == ')' ) {
+ if ( count <= 0 ) {
+ m_position = i + 1;
+ return result;
+ }
+ if ( count == 1 )
+ result.append( m_data.mid( sublistbegin, i - sublistbegin + 1 ) );
+ --count;
+ ++i;
+ continue;
+ }
+ if ( m_data.at(i) == ' ' ) {
+ ++i;
+ continue;
+ }
+ if ( m_data.at(i) == '"' ) {
+ if ( count > 0 ) {
+ m_position = i;
+ parseQuotedString();
+ i = m_position;
+ continue;
+ }
+ }
+ if ( m_data.at(i) == '[' ) {
+ concatToLast = true;
+ if ( result.isEmpty() ) {
+ result.append( QByteArray() );
+ }
+ result.last()+='[';
+ ++i;
+ continue;
+ }
+ if ( m_data.at(i) == ']' ) {
+ concatToLast = false;
+ result.last()+=']';
+ ++i;
+ continue;
+ }
+ if ( count == 0 ) {
+ m_position = i;
+ QByteArray ba;
+ if (hasLiteral()) {
+ while (!atLiteralEnd()) {
+ ba+=readLiteralPart();
+ }
+ } else {
+ ba = readString();
+ }
+
+ // We might sometime get some unwanted CRLF, but we're still not at the end
+ // of the list, would make further string reads fail so eat the CRLFs.
+ while ( ( m_position < m_data.size() ) && ( m_data.at(m_position) == '\r' || m_data.at(m_position) == '\n' ) ) {
+ m_position++;
+ }
+
+ i = m_position - 1;
+ if (concatToLast) {
+ result.last()+=ba;
+ } else {
+ result.append( ba );
+ }
+ }
+ ++i;
+ }
+
+ throw ImapParserException( "Something went very very wrong!" );
+}
+
+bool ImapStreamParser::hasResponseCode()
+{
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ int savedPos = m_position;
+ stripLeadingSpaces();
+ int pos = m_position;
+ m_position = savedPos;
+ if ( m_data.at(pos) == '[' )
+ {
+ m_position = pos + 1;
+ return true;
+ }
+
+ return false;
+}
+
+bool ImapStreamParser::atResponseCodeEnd()
+{
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ int savedPos = m_position;
+ stripLeadingSpaces();
+ int pos = m_position;
+ m_position = savedPos;
+ if ( m_data.at(pos) == ']' )
+ {
+ m_position = pos + 1;
+ return true;
+ }
+
+ return false;
+}
+
+QByteArray ImapStreamParser::parseQuotedString()
+{
+ QByteArray result;
+ if (! waitForMoreData( m_data.length() == 0 ) )
+ throw ImapParserException("Unable to read more data");
+ stripLeadingSpaces();
+ int end = m_position;
+ result.clear();
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+
+ bool foundSlash = false;
+ // quoted string
+ if ( m_data.at(m_position) == '"' ) {
+ ++m_position;
+ int i = m_position;
+ Q_FOREVER {
+ if ( !waitForMoreData( m_data.length() <= i ) )
+ {
+ m_position = i;
+ throw ImapParserException("Unable to read more data");
+ }
+ if ( m_data.at(i) == '\\' ) {
+ i += 2;
+ foundSlash = true;
+ continue;
+ }
+ if ( m_data.at(i) == '"' ) {
+ result = m_data.mid( m_position, i - m_position );
+ end = i + 1; // skip the '"'
+ break;
+ }
+ ++i;
+ }
+ }
+
+ // unquoted string
+ else {
+ bool reachedInputEnd = true;
+ int i = m_position;
+ Q_FOREVER {
+ if ( !waitForMoreData( m_data.length() <= i ) )
+ {
+ m_position = i;
+ throw ImapParserException("Unable to read more data");
+ }
+ if ( m_data.at(i) == ' ' || m_data.at(i) == '(' || m_data.at(i) == ')' || m_data.at(i) == '[' || m_data.at(i) == ']' || m_data.at(i) == '\n' || m_data.at(i) == '\r' || m_data.at(i) == '"') {
+ end = i;
+ reachedInputEnd = false;
+ break;
+ }
+ if (m_data.at(i) == '\\')
+ foundSlash = true;
+ i++;
+ }
+ if ( reachedInputEnd ) //FIXME: how can it get here?
+ end = m_data.length();
+
+ result = m_data.mid( m_position, end - m_position );
+ }
+
+ // strip quotes
+ if ( foundSlash ) {
+ while ( result.contains( "\\\"" ) )
+ result.replace( "\\\"", "\"" );
+ while ( result.contains( "\\\\" ) )
+ result.replace( "\\\\", "\\" );
+ }
+ m_position = end;
+ return result;
+}
+
+qint64 ImapStreamParser::readNumber( bool * ok )
+{
+ qint64 result;
+ if ( ok )
+ *ok = false;
+ if (! waitForMoreData( m_data.length() == 0 ) )
+ throw ImapParserException("Unable to read more data");
+ stripLeadingSpaces();
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ if ( m_position >= m_data.length() )
+ throw ImapParserException("Unable to read more data");
+ int i = m_position;
+ Q_FOREVER {
+ if ( !waitForMoreData( m_data.length() <= i ) )
+ {
+ m_position = i;
+ throw ImapParserException("Unable to read more data");
+ }
+ if ( !isdigit( m_data.at( i ) ) )
+ break;
+ ++i;
+ }
+ const QByteArray tmp = m_data.mid( m_position, i - m_position );
+ result = tmp.toLongLong( ok );
+ m_position = i;
+ return result;
+}
+
+void ImapStreamParser::stripLeadingSpaces()
+{
+ for ( int i = m_position; i < m_data.length(); ++i ) {
+ if ( m_data.at(i) != ' ' )
+ {
+ m_position = i;
+ return;
+ }
+ }
+ m_position = m_data.length();
+}
+
+bool ImapStreamParser::waitForMoreData( bool wait )
+{
+ if ( wait ) {
+ if ( m_socket->bytesAvailable() > 0 ||
+ m_socket->waitForReadyRead(30000) ) {
+ m_data.append( m_socket->readAll() );
+ } else
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ImapStreamParser::setData( const QByteArray &data )
+{
+ m_data = data;
+}
+
+QByteArray ImapStreamParser::readRemainingData()
+{
+ return m_data.mid(m_position);
+}
+
+int ImapStreamParser::availableDataSize() const
+{
+ return m_socket->bytesAvailable()+m_data.size()-m_position;
+}
+
+bool ImapStreamParser::atCommandEnd()
+{
+ int savedPos = m_position;
+ do {
+ if ( !waitForMoreData( m_position >= m_data.length() ) )
+ throw ImapParserException("Unable to read more data");
+ stripLeadingSpaces();
+ } while ( m_position >= m_data.size() );
+
+ if ( m_data.at(m_position) == '\n' || m_data.at(m_position) == '\r') {
+ if ( m_data.at(m_position) == '\r' )
+ ++m_position;
+ if ( m_position < m_data.length() && m_data.at(m_position) == '\n' )
+ ++m_position;
+
+ // We'd better empty m_data from time to time before it grows out of control
+ trimBuffer();
+
+ return true; //command end
+ }
+ m_position = savedPos;
+ return false; //something else
+}
+
+QByteArray ImapStreamParser::readUntilCommandEnd()
+{
+ QByteArray result;
+ int i = m_position;
+ int paranthesisBalance = 0;
+ Q_FOREVER {
+ if ( !waitForMoreData( m_data.length() <= i ) )
+ {
+ m_position = i;
+ throw ImapParserException("Unable to read more data");
+ }
+ if ( m_data.at(i) == '{' )
+ {
+ m_position = i - 1;
+ hasLiteral(); //init literal size
+ result.append(m_data.mid(i-1, m_position - i +1));
+ while (!atLiteralEnd())
+ {
+ result.append( readLiteralPart() );
+ }
+ i = m_position;
+ }
+ if ( m_data.at(i) == '(' )
+ paranthesisBalance++;
+ if ( m_data.at(i) == ')' )
+ paranthesisBalance--;
+ if ( ( i == m_data.length() && paranthesisBalance == 0 ) || m_data.at(i) == '\n' || m_data.at(i) == '\r')
+ break; //command end
+ result.append( m_data.at(i) );
+ ++i;
+ }
+ m_position = i;
+ atCommandEnd();
+ return result;
+}
+
+void ImapStreamParser::sendContinuationResponse( qint64 size )
+{
+ QByteArray block = "+ Ready for literal data (expecting "
+ + QByteArray::number( size ) + " bytes)\r\n";
+ m_socket->write(block);
+ m_socket->waitForBytesWritten(30000);
+}
+
+void ImapStreamParser::trimBuffer()
+{
+ if ( m_position < 4096 ) // right() is expensive, so don't do it for every line
+ return;
+ m_data = m_data.right(m_data.size()-m_position);
+ m_position = 0;
+}
diff --git a/kimap/imapstreamparser.h b/kimap/imapstreamparser.h
new file mode 100644
index 0000000..7c43d1d
--- /dev/null
+++ b/kimap/imapstreamparser.h
@@ -0,0 +1,216 @@
+/*
+ Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_IMAPSTREAMPARSER_P_H
+#define KIMAP_IMAPSTREAMPARSER_P_H
+
+#include "kimap_export.h"
+
+#include <exception>
+
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QString>
+
+class QIODevice;
+
+namespace KIMAP {
+
+class ImapParserException : public std::exception
+{
+ public:
+ ImapParserException( const char *what ) throw() : mWhat( what ) {}
+ ImapParserException( const QByteArray &what ) throw() : mWhat( what ) {}
+ ImapParserException( const QString &what ) throw() : mWhat( what.toUtf8() ) {}
+ ImapParserException( const ImapParserException &other ) throw() : std::exception( other ), mWhat( other.what() ) {}
+ virtual ~ImapParserException() throw() {}
+ const char *what() const throw() { return mWhat.constData(); }
+ virtual const char *type() const throw() { return "ImapParserException"; }
+ private:
+ QByteArray mWhat;
+};
+
+/**
+ Parser for IMAP messages that operates on a local socket stream.
+*/
+class KIMAP_EXPORT ImapStreamParser
+{
+ public:
+ /**
+ * Construct the parser.
+ * @param socket the local socket to work with.
+ * @param serverModeEnabled true if the parser has to assume we're writing a server (e.g. sends
+ * continuation message automatically)
+ */
+ explicit ImapStreamParser( QIODevice *socket, bool serverModeEnabled = false );
+
+ /**
+ * Destructor.
+ */
+ ~ImapStreamParser();
+
+ /**
+ * Get a string from the message. If the upcoming data is not a quoted string, unquoted string or a literal,
+ * the behavior is undefined. Use @ref hasString to be sure a string comes. This call might block.
+ * @return the next string from the message as an utf8 string
+ */
+ QString readUtf8String();
+
+ /**
+ * Same as above, but without decoding it to utf8.
+ * @return the next string from the message
+ */
+ QByteArray readString();
+
+ /**
+ * Get he next parenthesized list. If the upcoming data is not a parenthesized list,
+ * the behavior is undefined. Use @ref hasList to be sure a string comes. This call might block.
+ * @return the next parenthesized list.
+ */
+ QList<QByteArray> readParenthesizedList();
+
+
+ /**
+ * Get the next data as a number. This call might block.
+ * @param ok true if the data found was a number
+ * @return the number
+ */
+ qint64 readNumber( bool * ok = 0 );
+
+ /**
+ * Check if the next data is a string or not. This call might block.
+ * @return true if a string follows
+ */
+ bool hasString();
+
+ /**
+ * Check if the next data is a literal data or not. If a literal is found, the
+ * internal position pointer is set to the beginning of the literal data.
+ * This call might block.
+ * @return true if a literal follows
+ */
+ bool hasLiteral();
+
+ /**
+ * Read the next literal sequence. This might or might not be the full data. Example code to read a literal would be:
+ * @code
+ * ImapStreamParser parser;
+ * ...
+ * if (parser.hasLiteral())
+ * {
+ * while (!parser.atLiteralEnd())
+ * {
+ * QByteArray data = parser.readLiteralPart();
+ * // do something with the data
+ * }
+ * }
+ * @endcode
+ *
+ * This call might block.
+ *
+ * @return part of a literal data
+ */
+ QByteArray readLiteralPart();
+
+ /**
+ * Check if the literal data end was reached. See @ref hasLiteral and @ref readLiteralPart .
+ * @return true if the literal was completely read.
+ */
+ bool atLiteralEnd() const;
+
+ /**
+ * Check if the next data is a parenthesized list. This call might block.
+ * @return true if a parenthesized list comes.
+ */
+ bool hasList();
+
+ /**
+ * Check if the next data is a parenthesized list end. This call might block.
+ * @return true if a parenthesized list end.
+ */
+ bool atListEnd();
+
+ /**
+ * Check if the next data is a response code. This call might block.
+ * @return true if a response code comes.
+ */
+ bool hasResponseCode();
+
+ /**
+ * Check if the next data is a response code end. This call might block.
+ * @return true if a response code end.
+ */
+ bool atResponseCodeEnd();
+
+ /**
+ * Check if the command end was reached
+ * @return true if the end of command is reached
+ */
+ bool atCommandEnd();
+
+ /**
+ * Return everything that remained from the command.
+ * @return the remaining command data
+ */
+ QByteArray readUntilCommandEnd();
+
+ /**
+ * Return all the data that was read from the socket, but not processed yet.
+ * @return the remaining unprocessed data
+ */
+ QByteArray readRemainingData();
+
+ int availableDataSize() const;
+
+ void setData( const QByteArray &data );
+
+
+ private:
+ void stripLeadingSpaces();
+ QByteArray parseQuotedString();
+
+ /**
+ * If the condition is true, wait for more data to be available from the socket.
+ * If no data comes after a timeout (30000ms), it aborts and returns false.
+ * @param wait the condition
+ * @return true if more data is available
+ */
+ bool waitForMoreData( bool wait);
+
+ /**
+ * Inform the client to send more literal data.
+ */
+ void sendContinuationResponse( qint64 size );
+
+ /**
+ * Remove already read data from the internal buffer if necessary.
+ */
+ void trimBuffer();
+
+ QIODevice *m_socket;
+ bool m_isServerModeEnabled;
+ QByteArray m_data;
+ int m_position;
+ qint64 m_literalSize;
+};
+
+}
+
+#endif
diff --git a/kimap/job.cpp b/kimap/job.cpp
new file mode 100644
index 0000000..790ec0e
--- /dev/null
+++ b/kimap/job.cpp
@@ -0,0 +1,94 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "job.h"
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+using namespace KIMAP;
+
+Job::Job( Session *session )
+ : KJob( session ), d_ptr(new JobPrivate(session, i18n("Job")))
+{
+
+}
+
+Job::Job( JobPrivate &dd )
+ : KJob( dd.m_session ), d_ptr(&dd)
+{
+
+}
+
+Job::~Job()
+{
+ delete d_ptr;
+}
+
+Session *Job::session() const
+{
+ Q_D(const Job);
+ return d->m_session;
+}
+
+void Job::start()
+{
+ Q_D(Job);
+ d->sessionInternal()->addJob(this);
+}
+
+void Job::handleResponse(const Message &response)
+{
+ handleErrorReplies(response);
+}
+
+void Job::connectionLost()
+{
+ setError( KJob::UserDefinedError );
+ setErrorText( i18n("Connection to server lost.") );
+ emitResult();
+}
+
+Job::HandlerResponse Job::handleErrorReplies(const Message &response)
+{
+ Q_D(Job);
+// kDebug() << response.toString();
+
+ if ( !response.content.isEmpty()
+ && d->tags.contains( response.content.first().toString() ) ) {
+ if ( response.content.size() < 2 ) {
+ setErrorText( i18n("%1 failed, malformed reply from the server.", d->m_name) );
+ } else if ( response.content[1].toString() != "OK" ) {
+ setError( UserDefinedError );
+ setErrorText( i18n("%1 failed, server replied: %2", d->m_name, response.toString().constData()) );
+ }
+ d->tags.removeAll( response.content.first().toString() );
+ if ( d->tags.isEmpty() ) { // Only emit result when the last command returned
+ emitResult();
+ }
+ return Handled;
+ }
+
+ return NotHandled;
+}
+
+
diff --git a/kimap/job.h b/kimap/job.h
new file mode 100644
index 0000000..4656049
--- /dev/null
+++ b/kimap/job.h
@@ -0,0 +1,69 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_JOB_H
+#define KIMAP_JOB_H
+
+#include "kimap_export.h"
+
+#include <KDE/KJob>
+
+namespace KIMAP {
+
+class Session;
+class SessionPrivate;
+class JobPrivate;
+struct Message;
+
+class KIMAP_EXPORT Job : public KJob
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(Job)
+
+ friend class SessionPrivate;
+
+ public:
+ virtual ~Job();
+
+ Session *session() const;
+
+ virtual void start();
+
+ private:
+ virtual void doStart() = 0;
+ virtual void handleResponse(const Message &response);
+ virtual void connectionLost();
+
+ protected:
+ enum HandlerResponse {
+ Handled = 0,
+ NotHandled
+ };
+
+ HandlerResponse handleErrorReplies(const Message &response);
+
+ explicit Job( Session *session );
+ explicit Job( JobPrivate &dd );
+
+ JobPrivate *const d_ptr;
+};
+
+}
+
+#endif
diff --git a/kimap/job_p.h b/kimap/job_p.h
new file mode 100644
index 0000000..696dd4b
--- /dev/null
+++ b/kimap/job_p.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_JOB_P_H
+#define KIMAP_JOB_P_H
+
+#include "session.h"
+#include <KDE/KLocale>
+
+namespace KIMAP {
+
+class SessionPrivate;
+
+class JobPrivate
+{
+ public:
+ JobPrivate( Session *session, const QString& name ) : m_session(session) {
+ m_name = name;
+ }
+ virtual ~JobPrivate() { }
+
+ inline SessionPrivate *sessionInternal() {
+ return m_session->d;
+ }
+
+ inline const SessionPrivate *sessionInternal() const {
+ return m_session->d;
+ }
+
+ QList<QByteArray> tags;
+ Session *m_session;
+ QString m_name;
+};
+
+}
+
+#endif
diff --git a/kimap/kimap_export.h b/kimap/kimap_export.h
new file mode 100644
index 0000000..a38914f
--- /dev/null
+++ b/kimap/kimap_export.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of kdepimlibs.
+ Copyright (c) 2007 Allen Winter <winter@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KIMAP_EXPORT_H
+#define KIMAP_EXPORT_H
+
+#include <kdemacros.h>
+
+#ifndef KIMAP_EXPORT
+# if defined(KDEPIM_STATIC_LIBS)
+ /* No export/import for static libraries */
+# define KIMAP_EXPORT
+# elif defined(MAKE_KIMAP_LIB)
+ /* We are building this library */
+# define KIMAP_EXPORT KDE_EXPORT
+# else
+ /* We are using this library */
+# define KIMAP_EXPORT KDE_IMPORT
+# endif
+#endif
+
+#endif
diff --git a/kimap/listjob.cpp b/kimap/listjob.cpp
new file mode 100644
index 0000000..2f805ff
--- /dev/null
+++ b/kimap/listjob.cpp
@@ -0,0 +1,220 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "listjob.h"
+
+#include <boost/bind.hpp>
+#include <QtCore/QTimer>
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "rfccodecs.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class ListJobPrivate : public JobPrivate
+ {
+ public:
+ ListJobPrivate( ListJob *job, Session *session, const QString& name ) : JobPrivate(session, name), q(job), option(ListJob::NoOption) { }
+ ~ListJobPrivate() { }
+
+ void emitPendings()
+ {
+ if ( pendingDescriptors.isEmpty() ) {
+ return;
+ }
+
+ emit q->mailBoxesReceived( pendingDescriptors, pendingFlags );
+
+ pendingDescriptors.clear();
+ pendingFlags.clear();
+ }
+
+ ListJob * const q;
+
+ ListJob::Option option;
+ QList<MailBoxDescriptor> namespaces;
+ QByteArray command;
+
+ QTimer emitPendingsTimer;
+ QList<MailBoxDescriptor> pendingDescriptors;
+ QList< QList<QByteArray> > pendingFlags;
+ };
+}
+
+using namespace KIMAP;
+
+ListJob::ListJob( Session *session )
+ : Job( *new ListJobPrivate(this, session, i18n("List")) )
+{
+ Q_D(ListJob);
+ connect( &d->emitPendingsTimer, SIGNAL(timeout()),
+ this, SLOT(emitPendings()) );
+}
+
+ListJob::~ListJob()
+{
+}
+
+void ListJob::setIncludeUnsubscribed( bool include )
+{
+ Q_D(ListJob);
+ if (include) {
+ d->option = ListJob::IncludeUnsubscribed;
+ } else {
+ d->option = ListJob::NoOption;
+ }
+}
+
+bool ListJob::isIncludeUnsubscribed() const
+{
+ Q_D(const ListJob);
+ return ( d->option == ListJob::IncludeUnsubscribed );
+}
+
+void ListJob::setOption( Option option )
+{
+ Q_D(ListJob);
+ d->option = option;
+}
+
+ListJob::Option ListJob::option() const
+{
+ Q_D(const ListJob);
+ return d->option;
+}
+
+void ListJob::setQueriedNamespaces( const QList<MailBoxDescriptor> &namespaces )
+{
+ Q_D(ListJob);
+ d->namespaces = namespaces;
+}
+
+QList<MailBoxDescriptor> ListJob::queriedNamespaces() const
+{
+ Q_D(const ListJob);
+ return d->namespaces;
+}
+
+QList<MailBoxDescriptor> ListJob::mailBoxes() const
+{
+ return QList<MailBoxDescriptor>();
+}
+
+QMap< MailBoxDescriptor, QList<QByteArray> > ListJob::flags() const
+{
+ return QMap< MailBoxDescriptor, QList<QByteArray> >();
+}
+
+void ListJob::doStart()
+{
+ Q_D(ListJob);
+
+ switch (d->option) {
+ break;
+ case IncludeUnsubscribed:
+ d->command = "LIST";
+ break;
+ case IncludeFolderRoleFlags:
+ d->command = "XLIST";
+ break;
+ case NoOption:
+ default:
+ d->command = "LSUB";
+ }
+
+ d->emitPendingsTimer.start( 100 );
+
+ if ( d->namespaces.isEmpty() ) {
+ d->tags << d->sessionInternal()->sendCommand( d->command, "\"\" *" );
+ } else {
+ foreach ( const MailBoxDescriptor &descriptor, d->namespaces ) {
+ QString parameters = "\"\" \"%1\"";
+
+ if ( descriptor.name.endsWith( descriptor.separator ) ) {
+ QString name = encodeImapFolderName( descriptor.name );
+ name.chop( 1 );
+ d->tags << d->sessionInternal()->sendCommand( d->command,
+ parameters.arg( name ).toUtf8() );
+ }
+
+ d->tags << d->sessionInternal()->sendCommand( d->command,
+ parameters.arg( descriptor.name+'*' ).toUtf8() );
+ }
+ }
+}
+
+void ListJob::handleResponse( const Message &response )
+{
+ Q_D(ListJob);
+
+ // We can predict it'll be handled by handleErrorReplies() so stop
+ // the timer now so that result() will really be the last emitted signal.
+ if ( !response.content.isEmpty()
+ && d->tags.size() == 1
+ && d->tags.contains( response.content.first().toString() ) ) {
+ d->emitPendingsTimer.stop();
+ d->emitPendings();
+ }
+
+ if ( handleErrorReplies( response ) == NotHandled ) {
+ if ( response.content.size() >= 5 && response.content[1].toString() == d->command ) {
+ QList<QByteArray> flags = response.content[2].toList();
+ std::transform( flags.begin(), flags.end(), flags.begin(), boost::bind(&QByteArray::toLower, _1) );
+ QByteArray separator = response.content[3].toString();
+ if ( separator.isEmpty() ) {
+ // Defaults to / for servers reporting an empty list
+ // it's supposedly not a problem as servers doing that
+ // only do it for mailboxes with no child.
+ separator = "/"; //krazy:exclude=doublequote_chars since a QByteArray
+ }
+ Q_ASSERT(separator.size()==1);
+ QByteArray fullName;
+ for ( int i=4; i<response.content.size(); i++ ) {
+ fullName += response.content[i].toString() + ' ';
+ }
+ fullName.chop( 1 );
+
+ fullName = decodeImapFolderName( fullName );
+
+ MailBoxDescriptor mailBoxDescriptor;
+ mailBoxDescriptor.separator = QChar( separator[0] );
+ mailBoxDescriptor.name = QString::fromUtf8( fullName );
+ convertInboxName( mailBoxDescriptor );
+
+ d->pendingDescriptors << mailBoxDescriptor;
+ d->pendingFlags << flags;
+ }
+ }
+}
+
+void ListJob::convertInboxName(KIMAP::MailBoxDescriptor& descriptor)
+{
+ //Inbox must be case sensitive, according to the RFC, so make it always uppercase
+ QStringList pathParts = descriptor.name.split(descriptor.separator);
+ if ( !pathParts.isEmpty() && pathParts[0].compare( QLatin1String("INBOX"), Qt::CaseInsensitive ) == 0 ) {
+ pathParts.removeAt(0);
+ descriptor.name = QLatin1String("INBOX");
+ if ( !pathParts.isEmpty() )
+ descriptor.name += descriptor.separator + pathParts.join( descriptor.separator );
+ }
+}
+#include "moc_listjob.cpp"
diff --git a/kimap/listjob.h b/kimap/listjob.h
new file mode 100644
index 0000000..c90cea6
--- /dev/null
+++ b/kimap/listjob.h
@@ -0,0 +1,101 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_LISTJOB_H
+#define KIMAP_LISTJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class ListJobPrivate;
+
+struct KIMAP_EXPORT MailBoxDescriptor
+{
+ QString name;
+ QChar separator;
+
+ inline bool operator==(const MailBoxDescriptor &other) const
+ {
+ return other.name==name && other.separator==separator;
+ }
+
+ inline bool operator<(const MailBoxDescriptor &other) const
+ {
+ return other.name<name || (other.name==name && other.separator<separator);
+ }
+};
+
+class KIMAP_EXPORT ListJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(ListJob)
+
+ friend class SessionPrivate;
+
+ public:
+ enum Option {
+ NoOption = 0x0, /**< List only subscribed mailboxes. (Uses the LSUB IMAP command.) */
+ IncludeUnsubscribed, /**< List subscribed and unsubscribed mailboxes. (Uses the LIST IMAP command.) */
+ IncludeFolderRoleFlags /**< List subscribed and unsubscribed mailboxes with flags to identify standard mailboxes whose name may be localized.
+ The server must support the XLIST extension. */
+ };
+
+ explicit ListJob( Session *session );
+ virtual ~ListJob();
+
+ KDE_DEPRECATED void setIncludeUnsubscribed( bool include );
+ KDE_DEPRECATED bool isIncludeUnsubscribed() const;
+
+ void setOption( Option option );
+ Option option() const;
+
+ void setQueriedNamespaces( const QList<MailBoxDescriptor> &namespaces );
+ QList<MailBoxDescriptor> queriedNamespaces() const;
+
+ KDE_DEPRECATED QList<MailBoxDescriptor> mailBoxes() const;
+ KDE_DEPRECATED QMap< MailBoxDescriptor, QList<QByteArray> > flags() const;
+
+ Q_SIGNALS:
+ void mailBoxesReceived( const QList<KIMAP::MailBoxDescriptor> &descriptors,
+ const QList< QList<QByteArray> > &flags );
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+ private:
+ Q_PRIVATE_SLOT( d_func(), void emitPendings() )
+
+ /**
+ * @brief Converts a mailbox descriptor's name to uppercase if it is the Inbox or an Inbox subfolder.
+ * This is according to the RFC3501, 5.1. Mailbox Naming section.
+ *
+ * @param descriptor the descriptor to convert, conversion happens in place
+ **/
+ void convertInboxName( KIMAP::MailBoxDescriptor &descriptor );
+};
+
+}
+
+#endif
diff --git a/kimap/listrightsjob.cpp b/kimap/listrightsjob.cpp
new file mode 100644
index 0000000..b3492de
--- /dev/null
+++ b/kimap/listrightsjob.cpp
@@ -0,0 +1,107 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "listrightsjob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "acljobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class ListRightsJobPrivate : public AclJobBasePrivate
+ {
+ public:
+ ListRightsJobPrivate( Session *session, const QString& name ) : AclJobBasePrivate(session, name), defaultRights(Acl::None) {}
+ ~ListRightsJobPrivate() { }
+
+ Acl::Rights defaultRights;
+ QList<Acl::Rights> possibleRights;
+
+ };
+}
+
+using namespace KIMAP;
+
+ListRightsJob::ListRightsJob( Session *session )
+ : AclJobBase(*new ListRightsJobPrivate(session, i18n("ListRights")))
+{
+
+}
+
+ListRightsJob::~ListRightsJob()
+{
+}
+
+void ListRightsJob::doStart()
+{
+ Q_D(ListRightsJob);
+
+ d->tags << d->sessionInternal()->sendCommand( "LISTRIGHTS", '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + "\" \"" + d->id + "\"" );
+}
+
+void ListRightsJob::handleResponse( const Message &response )
+{
+ Q_D(ListRightsJob);
+
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 4
+ && response.content[1].toString() == "LISTRIGHTS" ) {
+ QByteArray s = response.content[4].toString();
+ d->defaultRights = Acl::rightsFromString(s);
+ int i = 5;
+ while ( i < response.content.size()) {
+ s = response.content[i].toString();
+ d->possibleRights.append(Acl::rightsFromString(s));
+ i++;
+ }
+ }
+ }
+}
+
+
+void ListRightsJob::setIdentifier( const QByteArray &identifier )
+{
+ Q_D(ListRightsJob);
+ d->setIdentifier(identifier);
+}
+
+QByteArray ListRightsJob::identifier()
+{
+ Q_D(ListRightsJob);
+ return d->identifier();
+}
+
+Acl::Rights ListRightsJob::defaultRights()
+{
+ Q_D(ListRightsJob);
+ return d->defaultRights;
+}
+
+QList<Acl::Rights> ListRightsJob::possibleRights()
+{
+ Q_D(ListRightsJob);
+ return d->possibleRights;
+}
+
+
diff --git a/kimap/listrightsjob.h b/kimap/listrightsjob.h
new file mode 100644
index 0000000..ed4850d
--- /dev/null
+++ b/kimap/listrightsjob.h
@@ -0,0 +1,122 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_LISTRIGHTSJOB_H
+#define KIMAP_LISTRIGHTSJOB_H
+
+#include "kimap_export.h"
+
+#include "acljobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class ListRightsJobPrivate;
+
+
+/**
+ * Lists the possible and automatic rights for
+ * an identifier on a mailbox
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * The user must have the Acl::Admin permission
+ * on the mailbox for this job to succeed (see
+ * MyRightsJob).
+ *
+ * This job requires that the server supports the ACL
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc4314.html">RFC 4314</a>.
+ */
+class KIMAP_EXPORT ListRightsJob : public AclJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(ListRightsJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit ListRightsJob( Session *session );
+ virtual ~ListRightsJob();
+
+ /**
+ * Sets the identifier that should be looked up
+ *
+ * The meaning of identifiers depends on the server implementation,
+ * with the following restrictions:
+ *
+ * - "anyone" means any authenticated user, including anonymous
+ * - an identifier starting with a minus sign ('-') indicates
+ * "negative rights": rights that should be taken away from
+ * matching users
+ *
+ * Other than the above restrictions, ACL identifiers are usually
+ * IMAP usernames, but could potentially be group names as well.
+ *
+ * Note that negative rights override positive rights: if
+ * "fred" and "-fred" are both assigned the 'w' right, the
+ * user "fred" will not have the 'w' right.
+ *
+ * @param identifier the identifier to list the rights for
+ */
+ void setIdentifier( const QByteArray &identifier );
+ /**
+ * The identifier that will be looked up
+ */
+ QByteArray identifier();
+
+ /**
+ * The rights that will always be assigned to the identifier,
+ * regardless of the access control list.
+ *
+ * For example, under the UNIX permission model, the owner
+ * of a mailbox will always have the Acl::Admin right.
+ */
+ Acl::Rights defaultRights();
+
+ /**
+ * The rights it is possible to assign to the identifier.
+ *
+ * The rights are grouped by those that are tied together.
+ * For each set of rights in the returned list, either all
+ * or none of those rights may be set, but not only some of
+ * them.
+ *
+ * For example, under the UNIX permission model, the following
+ * rights are all controlled by the "write" flag, and hence
+ * must either all be set or all be not set:
+ * - Acl::KeepSeen
+ * - Acl::Write
+ * - Acl::Insert
+ * - Acl::DeleteMessage
+ * - Acl::Expunge
+ */
+ QList<Acl::Rights> possibleRights();
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+};
+
+}
+
+#endif
diff --git a/kimap/loginjob.cpp b/kimap/loginjob.cpp
new file mode 100644
index 0000000..af0d912
--- /dev/null
+++ b/kimap/loginjob.cpp
@@ -0,0 +1,533 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "loginjob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+#include <ktcpsocket.h>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+#include "common.h"
+
+extern "C" {
+#include <sasl/sasl.h>
+}
+
+static sasl_callback_t callbacks[] = {
+ { SASL_CB_ECHOPROMPT, NULL, NULL },
+ { SASL_CB_NOECHOPROMPT, NULL, NULL },
+ { SASL_CB_GETREALM, NULL, NULL },
+ { SASL_CB_USER, NULL, NULL },
+ { SASL_CB_AUTHNAME, NULL, NULL },
+ { SASL_CB_PASS, NULL, NULL },
+ { SASL_CB_CANON_USER, NULL, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+namespace KIMAP
+{
+ class LoginJobPrivate : public JobPrivate
+ {
+ public:
+ enum AuthState {
+ StartTls = 0,
+ Capability,
+ Login,
+ Authenticate
+ };
+
+ LoginJobPrivate( LoginJob *job, Session *session, const QString& name ) : JobPrivate(session, name), q(job), encryptionMode(LoginJob::Unencrypted), authState(Login), plainLoginDisabled(false) {
+ conn = 0;
+ client_interact = 0;
+ }
+ ~LoginJobPrivate() { }
+ bool sasl_interact();
+
+ bool startAuthentication();
+ bool answerChallenge(const QByteArray &data);
+ void sslResponse(bool response);
+ void saveServerGreeting(const Message &response);
+
+ LoginJob *q;
+
+ QString userName;
+ QString password;
+ QString serverGreeting;
+
+ LoginJob::EncryptionMode encryptionMode;
+ QString authMode;
+ AuthState authState;
+ QStringList capabilities;
+ bool plainLoginDisabled;
+
+ sasl_conn_t *conn;
+ sasl_interact_t *client_interact;
+ };
+}
+
+using namespace KIMAP;
+
+bool LoginJobPrivate::sasl_interact()
+{
+ kDebug() <<"sasl_interact";
+ sasl_interact_t *interact = client_interact;
+
+ //some mechanisms do not require username && pass, so it doesn't need a popup
+ //window for getting this info
+ for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
+ if ( interact->id == SASL_CB_AUTHNAME ||
+ interact->id == SASL_CB_PASS ) {
+ //TODO: dialog for use name??
+ break;
+ }
+ }
+
+ interact = client_interact;
+ while( interact->id != SASL_CB_LIST_END ) {
+ kDebug() <<"SASL_INTERACT id:" << interact->id;
+ switch( interact->id ) {
+ case SASL_CB_USER:
+ case SASL_CB_AUTHNAME:
+ kDebug() <<"SASL_CB_[USER|AUTHNAME]: '" << userName <<"'";
+ interact->result = strdup( userName.toUtf8() );
+ interact->len = strlen( (const char *) interact->result );
+ break;
+ case SASL_CB_PASS:
+ kDebug() <<"SASL_CB_PASS: [hidden]";
+ interact->result = strdup( password.toUtf8() );
+ interact->len = strlen( (const char *) interact->result );
+ break;
+ default:
+ interact->result = 0;
+ interact->len = 0;
+ break;
+ }
+ interact++;
+ }
+ return true;
+}
+
+
+LoginJob::LoginJob( Session *session )
+ : Job( *new LoginJobPrivate(this, session, i18n("Login")) )
+{
+ Q_D(LoginJob);
+ connect(d->sessionInternal(), SIGNAL(encryptionNegotiationResult(bool)), this, SLOT(sslResponse(bool)));
+}
+
+LoginJob::~LoginJob()
+{
+}
+
+QString LoginJob::userName() const
+{
+ Q_D(const LoginJob);
+ return d->userName;
+}
+
+void LoginJob::setUserName( const QString &userName )
+{
+ Q_D(LoginJob);
+ d->userName = userName;
+}
+
+QString LoginJob::password() const
+{
+ Q_D(const LoginJob);
+ return d->password;
+}
+
+void LoginJob::setPassword( const QString &password )
+{
+ Q_D(LoginJob);
+ d->password = password;
+}
+
+void LoginJob::doStart()
+{
+ Q_D(LoginJob);
+
+ // Don't authenticate on a session in the authenticated state
+ if ( session()->state() == Session::Authenticated || session()->state() == Session::Selected ) {
+ setError( UserDefinedError );
+ setErrorText( i18n("IMAP session in the wrong state for authentication") );
+ emitResult();
+ return;
+ }
+
+ // Trigger encryption negotiation only if needed
+ EncryptionMode encryptionMode = d->encryptionMode;
+
+ switch ( d->sessionInternal()->negotiatedEncryption() ) {
+ case KTcpSocket::UnknownSslVersion:
+ break; // Do nothing the encryption mode still needs to be negotiated
+
+ // For the other cases, pretend we're going unencrypted as that's the
+ // encryption mode already set on the session
+ // (so for instance we won't issue another STARTTLS for nothing if that's
+ // not needed)
+ case KTcpSocket::SslV2:
+ if ( encryptionMode==SslV2 ) {
+ encryptionMode = Unencrypted;
+ }
+ break;
+ case KTcpSocket::SslV3:
+ if ( encryptionMode==SslV3 ) {
+ encryptionMode = Unencrypted;
+ }
+ break;
+ case KTcpSocket::TlsV1:
+ if ( encryptionMode==TlsV1 ) {
+ encryptionMode = Unencrypted;
+ }
+ break;
+ case KTcpSocket::AnySslVersion:
+ if ( encryptionMode==AnySslVersion ) {
+ encryptionMode = Unencrypted;
+ }
+ break;
+ }
+
+ if (encryptionMode == SslV2
+ || encryptionMode == SslV3
+ || encryptionMode == SslV3_1
+ || encryptionMode == AnySslVersion) {
+ KTcpSocket::SslVersion version = KTcpSocket::SslV2;
+ if (encryptionMode == SslV3)
+ version = KTcpSocket::SslV3;
+ if (encryptionMode == SslV3_1)
+ version = KTcpSocket::SslV3_1;
+ if (encryptionMode == AnySslVersion)
+ version = KTcpSocket::AnySslVersion;
+ d->sessionInternal()->startSsl(version);
+
+ } else if (encryptionMode == TlsV1) {
+ d->authState = LoginJobPrivate::StartTls;
+ d->tags << d->sessionInternal()->sendCommand( "STARTTLS" );
+
+ } else if (encryptionMode == Unencrypted ) {
+ if (d->authMode.isEmpty()) {
+ d->authState = LoginJobPrivate::Login;
+ d->tags << d->sessionInternal()->sendCommand( "LOGIN",
+ '"'+quoteIMAP( d->userName ).toUtf8()+'"'
+ +' '
+ +'"'+quoteIMAP(d->password ).toUtf8()+'"' );
+ } else {
+ if (!d->startAuthentication()) {
+ emitResult();
+ }
+ }
+ }
+}
+
+void LoginJob::handleResponse( const Message &response )
+{
+ Q_D(LoginJob);
+
+ //set the actual command name for standard responses
+ QString commandName = i18n("Login");
+ if (d->authState == LoginJobPrivate::Capability) {
+ commandName = i18n("Capability");
+ } else if (d->authState == LoginJobPrivate::StartTls) {
+ commandName = i18n("StartTls");
+ }
+
+ if ( d->authMode == QLatin1String( "PLAIN" ) && !response.content.isEmpty() && response.content.first().toString()=="+" ) {
+ if ( response.content.size()>1 && response.content.at( 1 ).toString()=="OK" ) {
+ return;
+ }
+
+ QByteArray challengeResponse;
+ challengeResponse+= '\0';
+ challengeResponse+= d->userName.toUtf8();
+ challengeResponse+= '\0';
+ challengeResponse+= d->password.toUtf8();
+ challengeResponse = challengeResponse.toBase64();
+ d->sessionInternal()->sendData( challengeResponse );
+
+ } else if ( !response.content.isEmpty()
+ && d->tags.contains( response.content.first().toString() ) ) {
+ if ( response.content.size() < 2 ) {
+ setErrorText( i18n("%1 failed, malformed reply from the server.", commandName) );
+ emitResult();
+ } else if ( response.content[1].toString() != "OK" ) {
+ //server replied with NO or BAD for SASL authentication
+ if (d->authState == LoginJobPrivate::Authenticate) {
+ sasl_dispose( &d->conn );
+ }
+
+ setError( UserDefinedError );
+ setErrorText( i18n("%1 failed, server replied: %2", commandName, response.toString().constData()) );
+ emitResult();
+ } else if ( response.content[1].toString() == "OK") {
+ if (d->authState == LoginJobPrivate::Authenticate) {
+ sasl_dispose( &d->conn ); //SASL authentication done
+ d->saveServerGreeting( response );
+ emitResult();
+ } else if (d->authState == LoginJobPrivate::Capability) {
+
+ //cleartext login, if enabled
+ if (d->authMode.isEmpty()) {
+ if (d->plainLoginDisabled) {
+ setError( UserDefinedError );
+ setErrorText( i18n("Login failed, plain login is disabled by the server.") );
+ emitResult();
+ } else {
+ d->authState = LoginJobPrivate::Login;
+ d->tags << d->sessionInternal()->sendCommand( "LOGIN",
+ '"'+quoteIMAP( d->userName ).toUtf8()+'"'
+ +' '
+ +'"'+quoteIMAP( d->password ).toUtf8()+'"');
+ }
+ }
+
+ //find the selected SASL authentication method
+ Q_FOREACH(const QString &capability, d->capabilities) {
+ if (capability.startsWith(QLatin1String("AUTH="))) {
+ QString authType = capability.mid(5);
+ if (authType == d->authMode) {
+ if (!d->startAuthentication()) {
+ emitResult(); //problem, we're done
+ }
+ }
+ }
+ }
+ } else if (d->authState == LoginJobPrivate::StartTls) {
+ d->sessionInternal()->startSsl(KTcpSocket::TlsV1);
+ } else {
+ d->saveServerGreeting( response );
+ emitResult(); //got an OK, command done
+ }
+ }
+ } else if ( response.content.size() >= 2 ) {
+ if ( d->authState == LoginJobPrivate::Authenticate ) {
+ if (!d->answerChallenge(QByteArray::fromBase64(response.content[1].toString()))) {
+ emitResult(); //error, we're done
+ }
+ } else if ( response.content[1].toString()=="CAPABILITY" ) {
+ bool authModeSupported = d->authMode.isEmpty();
+ for (int i = 2; i < response.content.size(); ++i) {
+ QString capability = response.content[i].toString();
+ d->capabilities << capability;
+ if (capability == "LOGINDISABLED") {
+ d->plainLoginDisabled = true;
+ }
+ QString authMode = capability.mid(5);
+ if (authMode == d->authMode) {
+ authModeSupported = true;
+ }
+ }
+ kDebug() << "Capabilities after STARTTLS: " << d->capabilities;
+ if (!authModeSupported) {
+ setError( UserDefinedError );
+ setErrorText( i18n("Login failed, authentication mode %1 is not supported by the server.", d->authMode) );
+ d->authState = LoginJobPrivate::Login; //just to treat the upcoming OK correctly
+ }
+ }
+ }
+}
+
+bool LoginJobPrivate::startAuthentication()
+{
+ //SASL authentication
+ if (!initSASL()) {
+ q->setError( LoginJob::UserDefinedError );
+ q->setErrorText( i18n("Login failed, client cannot initialize the SASL library.") );
+ return false;
+ }
+
+ authState = LoginJobPrivate::Authenticate;
+ const char *out = 0;
+ uint outlen = 0;
+ const char *mechusing = 0;
+
+ int result = sasl_client_new( "imap", m_session->hostName().toLatin1(), 0, 0, callbacks, 0, &conn );
+ if ( result != SASL_OK ) {
+ kDebug() <<"sasl_client_new failed with:" << result;
+ q->setError( LoginJob::UserDefinedError );
+ q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
+ return false;
+ }
+
+ do {
+ result = sasl_client_start(conn, authMode.toLatin1(), &client_interact, capabilities.contains("SASL-IR") ? &out : 0, &outlen, &mechusing);
+
+ if ( result == SASL_INTERACT ) {
+ if ( !sasl_interact() ) {
+ sasl_dispose( &conn );
+ q->setError( LoginJob::UserDefinedError ); //TODO: check up the actual error
+ return false;
+ }
+ }
+ } while ( result == SASL_INTERACT );
+
+ if ( result != SASL_CONTINUE && result != SASL_OK ) {
+ kDebug() <<"sasl_client_start failed with:" << result;
+ q->setError( LoginJob::UserDefinedError );
+ q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
+ sasl_dispose( &conn );
+ return false;
+ }
+
+ QByteArray tmp = QByteArray::fromRawData( out, outlen );
+ QByteArray challenge = tmp.toBase64();
+
+ if ( challenge.isEmpty() ) {
+ tags << sessionInternal()->sendCommand( "AUTHENTICATE", authMode.toLatin1() );
+ } else {
+ tags << sessionInternal()->sendCommand( "AUTHENTICATE", authMode.toLatin1() + ' ' + challenge );
+ }
+
+ return true;
+}
+
+bool LoginJobPrivate::answerChallenge(const QByteArray &data)
+{
+ QByteArray challenge = data;
+ int result = -1;
+ const char *out = 0;
+ uint outlen = 0;
+ do {
+ result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
+ challenge.size(),
+ &client_interact,
+ &out, &outlen);
+
+ if (result == SASL_INTERACT) {
+ if ( !sasl_interact() ) {
+ q->setError( LoginJob::UserDefinedError ); //TODO: check up the actual error
+ sasl_dispose( &conn );
+ return false;
+ }
+ }
+ } while ( result == SASL_INTERACT );
+
+ if ( result != SASL_CONTINUE && result != SASL_OK ) {
+ kDebug() <<"sasl_client_step failed with:" << result;
+ q->setError( LoginJob::UserDefinedError ); //TODO: check up the actual error
+ q->setErrorText( QString::fromUtf8( sasl_errdetail( conn ) ) );
+ sasl_dispose( &conn );
+ return false;
+ }
+
+ QByteArray tmp = QByteArray::fromRawData( out, outlen );
+ challenge = tmp.toBase64();
+
+ sessionInternal()->sendData( challenge );
+
+ return true;
+}
+
+void LoginJobPrivate::sslResponse(bool response)
+{
+ if (response) {
+ authState = LoginJobPrivate::Capability;
+ tags << sessionInternal()->sendCommand( "CAPABILITY" );
+ } else {
+ q->setError( LoginJob::UserDefinedError );
+ q->setErrorText( i18n("Login failed, TLS negotiation failed." ));
+ encryptionMode = LoginJob::Unencrypted;
+ q->emitResult();
+ }
+}
+
+void LoginJob::setEncryptionMode(EncryptionMode mode)
+{
+ Q_D(LoginJob);
+ d->encryptionMode = mode;
+}
+
+LoginJob::EncryptionMode LoginJob::encryptionMode()
+{
+ Q_D(LoginJob);
+ return d->encryptionMode;
+}
+
+void LoginJob::setAuthenticationMode(AuthenticationMode mode)
+{
+ Q_D(LoginJob);
+ switch (mode)
+ {
+ case ClearText: d->authMode = "";
+ break;
+ case Login: d->authMode = "LOGIN";
+ break;
+ case Plain: d->authMode = "PLAIN";
+ break;
+ case CramMD5: d->authMode = "CRAM-MD5";
+ break;
+ case DigestMD5: d->authMode = "DIGEST-MD5";
+ break;
+ case GSSAPI: d->authMode = "GSSAPI";
+ break;
+ case Anonymous: d->authMode = "ANONYMOUS";
+ break;
+ default:
+ d->authMode = "";
+ }
+}
+
+void LoginJob::connectionLost()
+{
+ Q_D(LoginJob);
+
+ //don't emit the result if the connection was lost before getting the tls result, as it can mean
+ //the TLS handshake failed and the socket was reconnected in normal mode
+ if (d->authState != LoginJobPrivate::StartTls) {
+ setError( ERR_COULD_NOT_CONNECT );
+ setErrorText( i18n("Connection to server lost.") );
+ emitResult();
+ }
+
+}
+
+void LoginJobPrivate::saveServerGreeting(const Message &response)
+{
+ // Concatenate the parts of the server response into a string, while dropping the first two parts
+ // (the response tag and the "OK" code), and being careful not to add useless extra whitespace.
+
+ for ( int i=2; i<response.content.size(); i++) {
+ if ( response.content.at(i).type()==Message::Part::List ) {
+ serverGreeting+='(';
+ foreach ( const QByteArray &item, response.content.at(i).toList() ) {
+ serverGreeting+=item+' ';
+ }
+ serverGreeting.chop(1);
+ serverGreeting+=") ";
+ } else {
+ serverGreeting+=response.content.at(i).toString()+' ';
+ }
+ }
+ serverGreeting.chop(1);
+}
+
+QString LoginJob::serverGreeting() const
+{
+ Q_D(const LoginJob);
+ return d->serverGreeting;
+}
+
+#include "moc_loginjob.cpp"
diff --git a/kimap/loginjob.h b/kimap/loginjob.h
new file mode 100644
index 0000000..0967348
--- /dev/null
+++ b/kimap/loginjob.h
@@ -0,0 +1,113 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_LOGINJOB_H
+#define KIMAP_LOGINJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class LoginJobPrivate;
+
+class KIMAP_EXPORT LoginJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(LoginJob)
+
+ friend class SessionPrivate;
+
+ public:
+ enum EncryptionMode {
+ Unencrypted = 0,
+ TlsV1,
+ SslV2,
+ SslV3,
+ SslV3_1,
+ AnySslVersion
+ };
+
+ enum AuthenticationMode {
+ ClearText = 0,
+ Login,
+ Plain,
+ CramMD5,
+ DigestMD5,
+ NTLM,
+ GSSAPI,
+ Anonymous
+ };
+
+ enum ErrorCode {
+ ERR_COULD_NOT_CONNECT = KJob::UserDefinedError + 23 // same as in kio
+ };
+
+ explicit LoginJob( Session *session );
+ virtual ~LoginJob();
+
+ QString userName() const;
+ void setUserName( const QString &userName );
+
+ QString password() const;
+ void setPassword( const QString &password );
+
+ /**
+ * Returns the server greeting, in case of a successful login.
+ * If the login wasn't successful, this method returns an empty string. Use errorString() to
+ * get the error message in this case.
+ *
+ * Note that the content of this response is not defined by the IMAP protocol and is
+ * implementation-dependent.
+ * @since 4.7
+ */
+ QString serverGreeting() const;
+
+ /**
+ * Set the encryption mode for the connection. In case an encryption mode is set, the caller
+ * MUST check the encryptionMode() result after executing the job, to see if the connection is
+ * encrypted or not (e.g handshaking failed).
+ * @param mode the encryption mode, see EncryptionModes
+ */
+ void setEncryptionMode(EncryptionMode mode);
+
+ /**
+ Get the encryption mode.
+ @return the currently active encryption mode
+ */
+ EncryptionMode encryptionMode();
+
+ void setAuthenticationMode( AuthenticationMode mode );
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse( const Message &response );
+ virtual void connectionLost();
+
+ private:
+ Q_PRIVATE_SLOT( d_func(), void sslResponse(bool) )
+};
+
+}
+
+#endif
diff --git a/kimap/logoutjob.cpp b/kimap/logoutjob.cpp
new file mode 100644
index 0000000..477c1bb
--- /dev/null
+++ b/kimap/logoutjob.cpp
@@ -0,0 +1,59 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "logoutjob.h"
+
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class LogoutJobPrivate : public JobPrivate
+ {
+ public:
+ LogoutJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~LogoutJobPrivate() { }
+ };
+}
+
+using namespace KIMAP;
+
+LogoutJob::LogoutJob( Session *session )
+ : Job( *new LogoutJobPrivate(session, i18n("Logout")) )
+{
+}
+
+LogoutJob::~LogoutJob()
+{
+}
+
+void LogoutJob::doStart()
+{
+ Q_D(LogoutJob);
+ d->tags << d->sessionInternal()->sendCommand( "LOGOUT" );
+}
+
+void LogoutJob::connectionLost()
+{
+ emitResult();
+}
+
diff --git a/kimap/logoutjob.h b/kimap/logoutjob.h
new file mode 100644
index 0000000..e3f6553
--- /dev/null
+++ b/kimap/logoutjob.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_LOGOUTJOB_H
+#define KIMAP_LOGOUTJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+class LogoutJobPrivate;
+
+class KIMAP_EXPORT LogoutJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(LogoutJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit LogoutJob( Session *session );
+ virtual ~LogoutJob();
+
+ protected:
+ virtual void doStart();
+ virtual void connectionLost();
+};
+
+}
+
+#endif
diff --git a/kimap/message_p.h b/kimap/message_p.h
new file mode 100644
index 0000000..9d703bf
--- /dev/null
+++ b/kimap/message_p.h
@@ -0,0 +1,97 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_MESSAGE_P_H
+#define KIMAP_MESSAGE_P_H
+
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QMetaType>
+
+namespace KIMAP {
+
+struct Message
+{
+ class Part
+ {
+ public:
+ enum Type { String = 0, List };
+
+ explicit Part(const QByteArray &string)
+ : m_type(String), m_string(string) { }
+ explicit Part(const QList<QByteArray> &list)
+ : m_type(List), m_list(list) { }
+
+ inline Type type() const { return m_type; }
+ inline QByteArray toString() const { return m_string; }
+ inline QList<QByteArray> toList() const { return m_list; }
+
+ private:
+ Type m_type;
+ QByteArray m_string;
+ QList<QByteArray> m_list;
+ };
+
+ inline QByteArray toString() const
+ {
+ QByteArray result;
+
+ foreach ( const Part &part, content ) {
+ if ( part.type()==Part::List ) {
+ result+='(';
+ foreach ( const QByteArray &item, part.toList() ) {
+ result+= ' ';
+ result+= item;
+ }
+ result+=" ) ";
+ } else {
+ result+= part.toString()+' ';
+ }
+ }
+
+ if ( !responseCode.isEmpty() ) {
+ result+="[ ";
+ foreach ( const Part &part, responseCode ) {
+ if ( part.type()==Part::List ) {
+ result+='(';
+ foreach ( const QByteArray &item, part.toList() ) {
+ result+= ' ';
+ result+= item;
+ }
+ result+=" ) ";
+ } else {
+ result+= part.toString()+' ';
+ }
+ }
+ result+=" ]";
+ }
+
+ return result;
+ }
+
+ QList<Part> content;
+ QList<Part> responseCode;
+};
+
+}
+
+Q_DECLARE_METATYPE(KIMAP::Message)
+static const int _kimap_messageTypeId = qRegisterMetaType<KIMAP::Message>();
+
+#endif
diff --git a/kimap/metadatajobbase.cpp b/kimap/metadatajobbase.cpp
new file mode 100644
index 0000000..da86124
--- /dev/null
+++ b/kimap/metadatajobbase.cpp
@@ -0,0 +1,71 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "metadatajobbase.h"
+#include "metadatajobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+using namespace KIMAP;
+
+MetaDataJobBase::MetaDataJobBase( Session *session )
+ : Job( *new MetaDataJobBasePrivate(session, i18n("MetaDataJobBase")) )
+{
+}
+
+
+MetaDataJobBase::MetaDataJobBase( JobPrivate &dd )
+ : Job(dd)
+{
+
+}
+
+MetaDataJobBase::~MetaDataJobBase()
+{
+}
+
+
+void MetaDataJobBase::setMailBox( const QString &mailBox )
+{
+ Q_D(MetaDataJobBase);
+ d->mailBox = mailBox;
+}
+
+QString MetaDataJobBase::mailBox() const
+{
+ Q_D(const MetaDataJobBase);
+ return d->mailBox;
+}
+
+void MetaDataJobBase::setServerCapability(const ServerCapability& capability)
+{
+ Q_D(MetaDataJobBase);
+ d->serverCapability = capability;
+}
+
+MetaDataJobBase::ServerCapability MetaDataJobBase::serverCapability() const
+{
+ Q_D(const MetaDataJobBase);
+ return d->serverCapability;
+}
+
+
diff --git a/kimap/metadatajobbase.h b/kimap/metadatajobbase.h
new file mode 100644
index 0000000..f7ad6e2
--- /dev/null
+++ b/kimap/metadatajobbase.h
@@ -0,0 +1,126 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_METADATAJOBBASE_H
+#define KIMAP_METADATAJOBBASE_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class MetaDataJobBasePrivate;
+
+/**
+ * Base class for jobs that operate on mailbox metadata
+ *
+ * Provides support for the IMAP METADATA extension; both the
+ * final RFC version
+ * (<a href="http://tools.ietf.org/html/rfc5464">RFC 5464</a>)
+ * and the older, incompatible draft version (known as ANNOTATEMORE)
+ * (<a
+ * href="http://tools.ietf.org/html/draft-daboo-imap-annotatemore-07"
+ * >draft-daboo-imap-annotatemore-07</a>).
+ *
+ * This class cannot be used directly, you must subclass it and reimplement
+ * at least the doStart() method.
+*/
+class KIMAP_EXPORT MetaDataJobBase : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(MetaDataJobBase)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit MetaDataJobBase( Session *session );
+ virtual ~MetaDataJobBase();
+
+ /**
+ * Represents the capability level of the server.
+ */
+ enum ServerCapability {
+ /**
+ * Used to indicate that the server supports the RFC 5464 version
+ * of the extension.
+ *
+ * This corresponds to the METADATA server capability.
+ */
+ Metadata = 0,
+ /**
+ * Used to indicate that the server supports the
+ * draft-daboo-imap-annotatemore-07 version of the extension.
+ *
+ * This corresponds to the ANNOTATEMORE server capability.
+ */
+ Annotatemore
+ };
+
+ /**
+ * Set the mailbox to act on
+ *
+ * This may be an empty string, in which case metadata for the
+ * server (rather than a specific mailbox) will be retrieved.
+ *
+ * @param mailBox the name of an existing mailbox, or an empty string
+ */
+ void setMailBox( const QString &mailBox );
+ /**
+ * The mailbox that will be acted upon.
+ *
+ * If this is an empty string, server metadata will be retrieved.
+ *
+ * @return a mailbox name, or an empty string
+ */
+ QString mailBox() const;
+
+ /**
+ * Set what version of the metadata extension to be compatible with.
+ *
+ * This will determine the commands that will be sent to the server.
+ *
+ * The draft for the metadata extension changed in an incompatible
+ * way between versions 7 and 8, and some servers support version 7.
+ * It should be possible to check which version the server supports
+ * using CapabilityJob: servers implementing
+ * draft-daboo-imap-annotatemore-07 should advertise the
+ * ANNOTATEMORE capability, whereas servers implementing the final
+ * RFC 5464 should advertise the METADATA capability.
+ *
+ * The default mode is Metadata.
+ *
+ * @param capability the version of the extension implemented by the server
+ */
+ void setServerCapability( const ServerCapability &capability );
+ /**
+ * The version of the metadata extension that will be used.
+ */
+ ServerCapability serverCapability() const;
+
+ protected:
+ MetaDataJobBase( JobPrivate &dd );
+
+};
+
+}
+
+#endif
diff --git a/kimap/metadatajobbase_p.h b/kimap/metadatajobbase_p.h
new file mode 100644
index 0000000..983a344
--- /dev/null
+++ b/kimap/metadatajobbase_p.h
@@ -0,0 +1,43 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_METADATAJOBBASE_P_H
+#define KIMAP_METADATAJOBBASE_P_H
+
+#include "metadatajobbase.h"
+#include "job_p.h"
+#include "message_p.h"
+#include "session.h"
+
+namespace KIMAP
+{
+ class MetaDataJobBasePrivate : public JobPrivate
+ {
+ public:
+ MetaDataJobBasePrivate( Session *session, const QString& name ) : JobPrivate(session, name), serverCapability(MetaDataJobBase::Metadata)
+ {}
+
+ ~MetaDataJobBasePrivate() { }
+
+ MetaDataJobBase::ServerCapability serverCapability;
+ QString mailBox;
+ };
+}
+
+#endif
diff --git a/kimap/myrightsjob.cpp b/kimap/myrightsjob.cpp
new file mode 100644
index 0000000..b3d78c0
--- /dev/null
+++ b/kimap/myrightsjob.cpp
@@ -0,0 +1,83 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "myrightsjob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "acljobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class MyRightsJobPrivate : public AclJobBasePrivate
+ {
+ public:
+ MyRightsJobPrivate( Session *session, const QString& name ) : AclJobBasePrivate(session, name), myRights(Acl::None) {}
+ ~MyRightsJobPrivate() { }
+
+ Acl::Rights myRights;
+ };
+}
+
+using namespace KIMAP;
+
+MyRightsJob::MyRightsJob( Session *session )
+ : AclJobBase( *new MyRightsJobPrivate(session, i18n("MyRights") ))
+{
+}
+
+MyRightsJob::~MyRightsJob()
+{
+}
+
+void MyRightsJob::doStart()
+{
+ Q_D(MyRightsJob);
+
+ d->tags << d->sessionInternal()->sendCommand( "MYRIGHTS", '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + '\"');
+}
+
+void MyRightsJob::handleResponse( const Message &response )
+{
+ Q_D(MyRightsJob);
+
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() == 4
+ && response.content[1].toString() == "MYRIGHTS" ) {
+ d->myRights = Acl::rightsFromString( response.content[3].toString() );
+ }
+ }
+}
+
+bool MyRightsJob::hasRightEnabled(Acl::Right right)
+{
+ Q_D(MyRightsJob);
+ return d->myRights & right;
+}
+
+Acl::Rights MyRightsJob::rights()
+{
+ Q_D(MyRightsJob);
+ return d->myRights;
+}
+
diff --git a/kimap/myrightsjob.h b/kimap/myrightsjob.h
new file mode 100644
index 0000000..4e57d0e
--- /dev/null
+++ b/kimap/myrightsjob.h
@@ -0,0 +1,93 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_MYRIGHTSJOB_H
+#define KIMAP_MYRIGHTSJOB_H
+
+#include "kimap_export.h"
+
+#include "acljobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class MyRightsJobPrivate;
+
+/**
+ * Determine the rights the currently-logged-in user
+ * has on the current mailbox.
+ *
+ * This should take into account the full access control
+ * list.
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * The current user must have one of the following rights
+ * on the mailbox for this job to succeed:
+ * - Acl::Lookup
+ * - Acl::Read
+ * - Acl::Insert
+ * - Acl::CreateMailbox
+ * - Acl::DeleteMailbox
+ * - Acl::Admin
+ *
+ * This job requires that the server supports the ACL
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc4314.html">RFC 4314</a>.
+ */
+class KIMAP_EXPORT MyRightsJob : public AclJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(MyRightsJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit MyRightsJob( Session *session );
+ virtual ~MyRightsJob();
+
+ /**
+ * Check whether the current user has the a particular right
+ * on the mailbox.
+ *
+ * The result of this method is undefined if the job has
+ * not yet completed.
+ *
+ * @param right the right to check for
+ */
+ bool hasRightEnabled(Acl::Right right);
+ /**
+ * Get the rights for the current user on the mailbox.
+ *
+ * The result of this method is undefined if the job has
+ * not yet completed.
+ */
+ Acl::Rights rights();
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+
+};
+
+}
+
+#endif
diff --git a/kimap/namespacejob.cpp b/kimap/namespacejob.cpp
new file mode 100644
index 0000000..6d01a1a
--- /dev/null
+++ b/kimap/namespacejob.cpp
@@ -0,0 +1,142 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "namespacejob.h"
+
+#include <KDE/KDebug>
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "listjob.h"
+#include "message_p.h"
+#include "rfccodecs.h"
+#include "session_p.h"
+#include "imapstreamparser.h"
+
+namespace KIMAP
+{
+ class NamespaceJobPrivate : public JobPrivate
+ {
+ public:
+ NamespaceJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~NamespaceJobPrivate() { }
+
+ QList<MailBoxDescriptor> processNamespaceList( const QList<QByteArray> &namespaceList )
+ {
+ QList<MailBoxDescriptor> result;
+
+ foreach ( const QByteArray &namespaceItem, namespaceList ) {
+ ImapStreamParser parser( 0 );
+ parser.setData( namespaceItem );
+
+ try {
+ QList<QByteArray> parts = parser.readParenthesizedList();
+ if ( parts.size() < 2 ) {
+ continue;
+ }
+ MailBoxDescriptor descriptor;
+ descriptor.name = QString::fromUtf8( decodeImapFolderName( parts[0] ) );
+ descriptor.separator = QChar( parts[1][0] );
+
+ result << descriptor;
+ } catch (KIMAP::ImapParserException e) {
+ qWarning() << "The stream parser raised an exception during namespace list parsing:" << e.what();
+ qWarning() << "namespacelist:" << namespaceList;
+ }
+
+ }
+
+ return result;
+ }
+
+ QList<MailBoxDescriptor> personalNamespaces;
+ QList<MailBoxDescriptor> userNamespaces;
+ QList<MailBoxDescriptor> sharedNamespaces;
+ };
+}
+
+using namespace KIMAP;
+
+NamespaceJob::NamespaceJob( Session *session )
+ : Job( *new NamespaceJobPrivate(session, i18n("Namespace")) )
+{
+}
+
+NamespaceJob::~NamespaceJob()
+{
+}
+
+QList<MailBoxDescriptor> NamespaceJob::personalNamespaces() const
+{
+ Q_D(const NamespaceJob);
+ return d->personalNamespaces;
+}
+
+QList<MailBoxDescriptor> NamespaceJob::userNamespaces() const
+{
+ Q_D(const NamespaceJob);
+ return d->userNamespaces;
+}
+
+QList<MailBoxDescriptor> NamespaceJob::sharedNamespaces() const
+{
+ Q_D(const NamespaceJob);
+ return d->sharedNamespaces;
+}
+
+bool NamespaceJob::containsEmptyNamespace() const
+{
+ Q_D(const NamespaceJob);
+ QList<MailBoxDescriptor> completeList = d->personalNamespaces
+ + d->userNamespaces
+ + d->sharedNamespaces;
+
+ foreach ( const MailBoxDescriptor &descriptor, completeList ) {
+ if ( descriptor.name.isEmpty() ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void NamespaceJob::doStart()
+{
+ Q_D(NamespaceJob);
+ d->tags << d->sessionInternal()->sendCommand( "NAMESPACE" );
+}
+
+void NamespaceJob::handleResponse( const Message &response )
+{
+ Q_D(NamespaceJob);
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 5
+ && response.content[1].toString()=="NAMESPACE" ) {
+ // Personal namespaces
+ d->personalNamespaces = d->processNamespaceList( response.content[2].toList() );
+
+ // User namespaces
+ d->userNamespaces = d->processNamespaceList( response.content[3].toList() );
+
+ // Shared namespaces
+ d->sharedNamespaces = d->processNamespaceList( response.content[4].toList() );
+ }
+ }
+}
+
diff --git a/kimap/namespacejob.h b/kimap/namespacejob.h
new file mode 100644
index 0000000..130c788
--- /dev/null
+++ b/kimap/namespacejob.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_NAMESPACEJOB_H
+#define KIMAP_NAMESPACEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+struct MailBoxDescriptor;
+class NamespaceJobPrivate;
+
+class KIMAP_EXPORT NamespaceJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(NamespaceJob)
+
+ friend class SessionPrivate;
+
+ public:
+ NamespaceJob( Session *session );
+ virtual ~NamespaceJob();
+
+ QList<MailBoxDescriptor> personalNamespaces() const;
+ QList<MailBoxDescriptor> userNamespaces() const;
+ QList<MailBoxDescriptor> sharedNamespaces() const;
+
+ bool containsEmptyNamespace() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse( const Message &response );
+};
+
+}
+
+#endif
diff --git a/kimap/quotajobbase.cpp b/kimap/quotajobbase.cpp
new file mode 100644
index 0000000..6b6d2af
--- /dev/null
+++ b/kimap/quotajobbase.cpp
@@ -0,0 +1,91 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "quotajobbase.h"
+#include "quotajobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+using namespace KIMAP;
+
+QMap<QByteArray, QPair<qint64, qint64> > QuotaJobBasePrivate::readQuota( const Message::Part &content )
+{
+ QMap<QByteArray, QPair<qint64, qint64> > quotaMap;
+ QList<QByteArray> quotas = content.toList();
+
+ int i = 0;
+ while ( i < quotas.size() - 2 ) {
+ QByteArray resource = quotas[i].toUpper();
+ qint64 usage = quotas[i+1].toInt();
+ qint64 limit = quotas[i+2].toInt();
+ quotaMap[resource] = qMakePair(usage, limit);
+ i += 3;
+ }
+
+ return quotaMap;
+}
+
+QuotaJobBase::QuotaJobBase( Session *session )
+ : Job( *new QuotaJobBasePrivate(session, i18n("QuotaJobBase")) )
+{
+}
+
+
+QuotaJobBase::QuotaJobBase( JobPrivate &dd )
+ : Job(dd)
+{
+
+}
+
+QuotaJobBase::~QuotaJobBase()
+{
+}
+
+
+qint64 QuotaJobBase::usage(const QByteArray& resource)
+{
+ Q_D(QuotaJobBase);
+
+ QByteArray r = resource.toUpper();
+
+ if (d->quota.contains(r)) {
+ return d->quota[r].first;
+ }
+
+ return -1;
+}
+
+qint64 QuotaJobBase::limit(const QByteArray& resource)
+{
+ Q_D(QuotaJobBase);
+
+ QByteArray r = resource.toUpper();
+
+ if (d->quota.contains(r)) {
+ return d->quota[r].second;
+ }
+
+ return -1;
+}
+
+
+
diff --git a/kimap/quotajobbase.h b/kimap/quotajobbase.h
new file mode 100644
index 0000000..640ca0d
--- /dev/null
+++ b/kimap/quotajobbase.h
@@ -0,0 +1,99 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_QUOTAJOBBASE_H
+#define KIMAP_QUOTAJOBBASE_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class QuotaJobBasePrivate;
+
+/**
+ * Base class for jobs that operate on mailbox quotas
+ *
+ * Provides support for the IMAP QUOTA extension, as defined by
+ * <a href="http://www.apps.ietf.org/rfc/rfc2087.html" title="IMAP QUOTA extension">RFC 2087</a>.
+ *
+ * This class cannot be used directly, you must subclass it and reimplement
+ * at least the doStart() method.
+*/
+class KIMAP_EXPORT QuotaJobBase : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QuotaJobBase)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit QuotaJobBase( Session *session );
+ virtual ~QuotaJobBase();
+
+ /**
+ * Get the current usage for a resource.
+ *
+ * All quota jobs will normally cause the server to return
+ * details of resource usage for all resources that were
+ * queried or modified by the job.
+ *
+ * Note that RFC 2087 is slightly ambiguous about whether
+ * SETQUOTA will cause this information to be sent by the
+ * server.
+ *
+ * Note that if there is no limit for a resource, the
+ * server will not provide information about resource
+ * usage.
+ *
+ * @param resource the resource to get the usage for
+ * @return the resource usage in appropriate units, or -1
+ * if the usage is unknown or there is no
+ * limit on the resource
+ */
+ qint64 usage(const QByteArray& resource);
+ /**
+ * Get the current limit for a resource.
+ *
+ * All quota jobs will normally cause the server to return
+ * details of resource limits for all resources that were
+ * queried or modified by the job.
+ *
+ * Note that RFC 2087 is slightly ambiguous about whether
+ * SETQUOTA will cause this information to be sent by the
+ * server.
+ *
+ * @param resource the resource to get the limit for
+ * @return the resource limit in appropriate units, or -1
+ * if the limit is unknown or there is no limit
+ * on the resource
+ */
+ qint64 limit(const QByteArray& resource);
+
+ protected:
+ QuotaJobBase( JobPrivate &dd );
+
+};
+
+}
+
+#endif
diff --git a/kimap/quotajobbase_p.h b/kimap/quotajobbase_p.h
new file mode 100644
index 0000000..aedc752
--- /dev/null
+++ b/kimap/quotajobbase_p.h
@@ -0,0 +1,42 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_QUOTAJOBBASE_P_H
+#define KIMAP_QUOTAJOBBASE_P_H
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session.h"
+
+namespace KIMAP
+{
+ class QuotaJobBasePrivate : public JobPrivate
+ {
+ public:
+ QuotaJobBasePrivate( Session *session, const QString& name ) : JobPrivate(session, name)
+ {}
+
+ ~QuotaJobBasePrivate() { }
+ QMap<QByteArray, QPair<qint64, qint64> > readQuota( const Message::Part &content );
+
+ QMap<QByteArray, QPair<qint64, qint64> > quota;
+ };
+}
+
+#endif
diff --git a/kimap/renamejob.cpp b/kimap/renamejob.cpp
new file mode 100644
index 0000000..e8b9cd3
--- /dev/null
+++ b/kimap/renamejob.cpp
@@ -0,0 +1,85 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "renamejob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class RenameJobPrivate : public JobPrivate
+ {
+ public:
+ RenameJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~RenameJobPrivate() { }
+
+ QString sourceMailBox;
+ QString destinationMailBox;
+ };
+}
+
+using namespace KIMAP;
+
+RenameJob::RenameJob( Session *session )
+ : Job( *new RenameJobPrivate(session, i18n("Rename")) )
+{
+}
+
+RenameJob::~RenameJob()
+{
+}
+
+void RenameJob::doStart()
+{
+ Q_D(RenameJob);
+ d->tags << d->sessionInternal()->sendCommand( "RENAME", '\"' + KIMAP::encodeImapFolderName( d->sourceMailBox.toUtf8() ) + "\" \""
+ + KIMAP::encodeImapFolderName( d->destinationMailBox.toUtf8() )+ '\"' );
+}
+
+void RenameJob::setSourceMailBox( const QString &mailBox )
+{
+ Q_D(RenameJob);
+ d->sourceMailBox = mailBox;
+}
+
+QString RenameJob::sourceMailBox() const
+{
+ Q_D(const RenameJob);
+ return d->sourceMailBox;
+}
+
+void RenameJob::setDestinationMailBox( const QString &mailBox )
+{
+ Q_D(RenameJob);
+ d->destinationMailBox = mailBox;
+}
+
+QString RenameJob::destinationMailBox() const
+{
+ Q_D(const RenameJob);
+ return d->destinationMailBox;
+}
+
+
diff --git a/kimap/renamejob.h b/kimap/renamejob.h
new file mode 100644
index 0000000..84ae96a
--- /dev/null
+++ b/kimap/renamejob.h
@@ -0,0 +1,63 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_RENAMEJOB_H
+#define KIMAP_RENAMEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+class RenameJobPrivate;
+
+class KIMAP_EXPORT RenameJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(RenameJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit RenameJob( Session *session );
+ virtual ~RenameJob();
+
+ /**
+ * Set the name of the mailbox that will be renamed.
+ * @param mailBox the original name of the mailbox
+ */
+ void setSourceMailBox( const QString &mailBox );
+ QString sourceMailBox() const;
+
+ /**
+ * The new name of the mailbox, see setMailBox.
+ * @param mailBox the new mailbox name
+ */
+ void setDestinationMailBox( const QString &mailBox );
+ QString destinationMailBox() const;
+
+ protected:
+ virtual void doStart();
+};
+
+}
+
+#endif
diff --git a/kimap/rfccodecs.cpp b/kimap/rfccodecs.cpp
new file mode 100644
index 0000000..ae0141a
--- /dev/null
+++ b/kimap/rfccodecs.cpp
@@ -0,0 +1,659 @@
+/**********************************************************************
+ *
+ * rfccodecs.cpp - handler for various rfc/mime encodings
+ * Copyright (C) 2000 s.carstens@gmx.de
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ *********************************************************************/
+/**
+ * @file
+ * This file is part of the IMAP support library and defines the
+ * RfcCodecs class.
+ *
+ * @brief
+ * Defines the RfcCodecs class.
+ *
+ * @author Sven Carstens
+ */
+
+#include "rfccodecs.h"
+
+#include <ctype.h>
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <QtCore/QTextCodec>
+#include <QtCore/QBuffer>
+#include <QtCore/QRegExp>
+#include <QtCore/QByteArray>
+#include <QtCore/QLatin1Char>
+#include <kcodecs.h>
+
+using namespace KIMAP;
+
+// This part taken from rfc 2192 IMAP URL Scheme. C. Newman. September 1997.
+// adapted to QT-Toolkit by Sven Carstens <s.carstens@gmx.de> 2000
+
+//@cond PRIVATE
+static const unsigned char base64chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
+#define UNDEFINED 64
+#define MAXLINE 76
+static const char especials[17] = "()<>@,;:\"/[]?.= ";
+
+/* UTF16 definitions */
+#define UTF16MASK 0x03FFUL
+#define UTF16SHIFT 10
+#define UTF16BASE 0x10000UL
+#define UTF16HIGHSTART 0xD800UL
+#define UTF16HIGHEND 0xDBFFUL
+#define UTF16LOSTART 0xDC00UL
+#define UTF16LOEND 0xDFFFUL
+//@endcond
+
+//-----------------------------------------------------------------------------
+QByteArray KIMAP::decodeImapFolderName( const QByteArray &inSrc )
+{
+ unsigned char c, i, bitcount;
+ unsigned long ucs4, utf16, bitbuf;
+ unsigned char base64[256], utf8[6];
+ unsigned int srcPtr = 0;
+ QByteArray dst;
+ QByteArray src = inSrc;
+ uint srcLen = inSrc.length();
+
+ /* initialize modified base64 decoding table */
+ memset( base64, UNDEFINED, sizeof( base64 ) );
+ for ( i = 0; i < sizeof( base64chars ); ++i ) {
+ base64[(int)base64chars[i]] = i;
+ }
+
+ /* loop until end of string */
+ while ( srcPtr < srcLen ) {
+ c = src[srcPtr++];
+ /* deal with literal characters and &- */
+ if ( c != '&' || src[srcPtr] == '-' ) {
+ /* encode literally */
+ dst += c;
+ /* skip over the '-' if this is an &- sequence */
+ if ( c == '&' ) {
+ srcPtr++;
+ }
+ } else {
+ /* convert modified UTF-7 -> UTF-16 -> UCS-4 -> UTF-8 -> HEX */
+ bitbuf = 0;
+ bitcount = 0;
+ ucs4 = 0;
+ while ( ( c = base64[(unsigned char)src[srcPtr]] ) != UNDEFINED ) {
+ ++srcPtr;
+ bitbuf = ( bitbuf << 6 ) | c;
+ bitcount += 6;
+ /* enough bits for a UTF-16 character? */
+ if ( bitcount >= 16 ) {
+ bitcount -= 16;
+ utf16 = ( bitcount ? bitbuf >> bitcount : bitbuf ) & 0xffff;
+ /* convert UTF16 to UCS4 */
+ if ( utf16 >= UTF16HIGHSTART && utf16 <= UTF16HIGHEND ) {
+ ucs4 = ( utf16 - UTF16HIGHSTART ) << UTF16SHIFT;
+ continue;
+ } else if ( utf16 >= UTF16LOSTART && utf16 <= UTF16LOEND ) {
+ ucs4 += utf16 - UTF16LOSTART + UTF16BASE;
+ } else {
+ ucs4 = utf16;
+ }
+ /* convert UTF-16 range of UCS4 to UTF-8 */
+ if ( ucs4 <= 0x7fUL ) {
+ utf8[0] = ucs4;
+ i = 1;
+ } else if ( ucs4 <= 0x7ffUL ) {
+ utf8[0] = 0xc0 | ( ucs4 >> 6 );
+ utf8[1] = 0x80 | ( ucs4 & 0x3f );
+ i = 2;
+ } else if ( ucs4 <= 0xffffUL ) {
+ utf8[0] = 0xe0 | ( ucs4 >> 12 );
+ utf8[1] = 0x80 | ( ( ucs4 >> 6 ) & 0x3f );
+ utf8[2] = 0x80 | ( ucs4 & 0x3f );
+ i = 3;
+ } else {
+ utf8[0] = 0xf0 | ( ucs4 >> 18 );
+ utf8[1] = 0x80 | ( ( ucs4 >> 12 ) & 0x3f );
+ utf8[2] = 0x80 | ( ( ucs4 >> 6 ) & 0x3f );
+ utf8[3] = 0x80 | ( ucs4 & 0x3f );
+ i = 4;
+ }
+ /* copy it */
+ for ( c = 0; c < i; ++c ) {
+ dst += utf8[c];
+ }
+ }
+ }
+ /* skip over trailing '-' in modified UTF-7 encoding */
+ if ( src[srcPtr] == '-' ) {
+ ++srcPtr;
+ }
+ }
+ }
+ return dst;
+}
+
+QString KIMAP::decodeImapFolderName( const QString &inSrc )
+{
+ return QString::fromUtf8( decodeImapFolderName( inSrc.toUtf8() ).data() );
+}
+
+//-----------------------------------------------------------------------------
+
+QByteArray KIMAP::quoteIMAP( const QByteArray &src )
+{
+ uint len = src.length();
+ QByteArray result;
+ result.reserve( 2 * len );
+ for ( unsigned int i = 0; i < len; i++ ) {
+ if ( src[i] == '"' || src[i] == '\\' ) {
+ result += '\\';
+ }
+ result += src[i];
+ }
+ result.squeeze();
+ return result;
+}
+
+QString KIMAP::quoteIMAP( const QString &src )
+{
+ uint len = src.length();
+ QString result;
+ result.reserve( 2 * len );
+ for ( unsigned int i = 0; i < len; i++ ) {
+ if ( src[i] == '"' || src[i] == '\\' ) {
+ result += '\\';
+ }
+ result += src[i];
+ }
+ //result.squeeze(); - unnecessary and slow
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+QString KIMAP::encodeImapFolderName( const QString &inSrc )
+{
+ return QString::fromUtf8( encodeImapFolderName( inSrc.toUtf8() ).data() );
+}
+
+QByteArray KIMAP::encodeImapFolderName( const QByteArray &inSrc )
+{
+ unsigned int utf8pos, utf8total, c, utf7mode, bitstogo, utf16flag;
+ unsigned int ucs4, bitbuf;
+ QByteArray src = inSrc;
+ QByteArray dst;
+
+ int srcPtr = 0;
+ utf7mode = 0;
+ utf8total = 0;
+ bitstogo = 0;
+ utf8pos = 0;
+ bitbuf = 0;
+ ucs4 = 0;
+ while ( srcPtr < src.length () ) {
+ c = (unsigned char)src[srcPtr++];
+ /* normal character? */
+ if ( c >= ' ' && c <= '~' ) {
+ /* switch out of UTF-7 mode */
+ if ( utf7mode ) {
+ if ( bitstogo ) {
+ dst += base64chars[( bitbuf << ( 6 - bitstogo ) ) & 0x3F];
+ bitstogo = 0;
+ }
+ dst += '-';
+ utf7mode = 0;
+ }
+ dst += c;
+ /* encode '&' as '&-' */
+ if ( c == '&' ) {
+ dst += '-';
+ }
+ continue;
+ }
+ /* switch to UTF-7 mode */
+ if ( !utf7mode ) {
+ dst += '&';
+ utf7mode = 1;
+ }
+ /* Encode US-ASCII characters as themselves */
+ if ( c < 0x80 ) {
+ ucs4 = c;
+ utf8total = 1;
+ } else if ( utf8total ) {
+ /* save UTF8 bits into UCS4 */
+ ucs4 = ( ucs4 << 6 ) | ( c & 0x3FUL );
+ if ( ++utf8pos < utf8total ) {
+ continue;
+ }
+ } else {
+ utf8pos = 1;
+ if ( c < 0xE0 ) {
+ utf8total = 2;
+ ucs4 = c & 0x1F;
+ } else if ( c < 0xF0 ) {
+ utf8total = 3;
+ ucs4 = c & 0x0F;
+ } else {
+ /* NOTE: can't convert UTF8 sequences longer than 4 */
+ utf8total = 4;
+ ucs4 = c & 0x03;
+ }
+ continue;
+ }
+ /* loop to split ucs4 into two utf16 chars if necessary */
+ utf8total = 0;
+ do
+ {
+ if ( ucs4 >= UTF16BASE ) {
+ ucs4 -= UTF16BASE;
+ bitbuf =
+ ( bitbuf << 16 ) | ( ( ucs4 >> UTF16SHIFT ) + UTF16HIGHSTART );
+ ucs4 = ( ucs4 & UTF16MASK ) + UTF16LOSTART;
+ utf16flag = 1;
+ } else {
+ bitbuf = ( bitbuf << 16 ) | ucs4;
+ utf16flag = 0;
+ }
+ bitstogo += 16;
+ /* spew out base64 */
+ while ( bitstogo >= 6 ) {
+ bitstogo -= 6;
+ dst +=
+ base64chars[( bitstogo ? ( bitbuf >> bitstogo ) : bitbuf ) & 0x3F];
+ }
+ }
+ while ( utf16flag );
+ }
+ /* if in UTF-7 mode, finish in ASCII */
+ if ( utf7mode ) {
+ if ( bitstogo ) {
+ dst += base64chars[( bitbuf << ( 6 - bitstogo ) ) & 0x3F];
+ }
+ dst += '-';
+ }
+ return quoteIMAP( dst );
+}
+
+//-----------------------------------------------------------------------------
+QTextCodec *KIMAP::codecForName( const QString &str )
+{
+ if ( str.isEmpty () ) {
+ return 0;
+ }
+ return QTextCodec::codecForName ( str.toLower ().
+ replace ( "windows", "cp" ).toLatin1 () );
+}
+
+//-----------------------------------------------------------------------------
+const QString KIMAP::decodeRFC2047String( const QString &str )
+{
+ QString throw_away;
+
+ return decodeRFC2047String( str, throw_away );
+}
+
+//-----------------------------------------------------------------------------
+const QString KIMAP::decodeRFC2047String( const QString &str,
+ QString &charset )
+{
+ QString throw_away;
+
+ return decodeRFC2047String( str, charset, throw_away );
+}
+
+//-----------------------------------------------------------------------------
+const QString KIMAP::decodeRFC2047String( const QString &str,
+ QString &charset,
+ QString &language )
+{
+ //do we have a rfc string
+ if ( !str.contains( "=?" ) ) {
+ return str;
+ }
+
+ // FIXME get rid of the conversion?
+ QByteArray aStr = str.toAscii (); // QString.length() means Unicode chars
+ QByteArray result;
+ char *pos, *beg, *end, *mid = 0;
+ QByteArray cstr;
+ char encoding = 0, ch;
+ bool valid;
+ const int maxLen = 200;
+ int i;
+
+// result.truncate(aStr.length());
+ for ( pos = aStr.data (); *pos; pos++ ) {
+ if ( pos[0] != '=' || pos[1] != '?' ) {
+ result += *pos;
+ continue;
+ }
+ beg = pos + 2;
+ end = beg;
+ valid = true;
+ // parse charset name
+ for ( i = 2, pos += 2;
+ i < maxLen &&
+ ( *pos != '?' && ( ispunct( *pos ) || isalnum ( *pos ) ) );
+ i++ )
+ pos++;
+ if ( *pos != '?' || i < 4 || i >= maxLen ) {
+ valid = false;
+ } else {
+ charset = QByteArray( beg, i - 1 ); // -2 + 1 for the zero
+ int pt = charset.lastIndexOf( '*' );
+ if ( pt != -1 ) {
+ // save language for later usage
+ language = charset.right( charset.length () - pt - 1 );
+
+ // tie off language as defined in rfc2047
+ charset.truncate( pt );
+ }
+ // get encoding and check delimiting question marks
+ encoding = toupper( pos[1] );
+ if ( pos[2] != '?' ||
+ ( encoding != 'Q' && encoding != 'B' &&
+ encoding != 'q' && encoding != 'b' ) ) {
+ valid = false;
+ }
+ pos += 3;
+ i += 3;
+// kDebug() << "Charset:" << charset << "- Language:" << language << "-'" << pos << "'";
+ }
+ if ( valid ) {
+ mid = pos;
+ // search for end of encoded part
+ while ( i < maxLen && *pos && !( *pos == '?' && *( pos + 1 ) == '=' ) ) {
+ i++;
+ pos++;
+ }
+ end = pos + 2;//end now points to the first char after the encoded string
+ if ( i >= maxLen || !*pos ) {
+ valid = false;
+ }
+ }
+ if ( valid ) {
+ ch = *pos;
+ *pos = '\0';
+ cstr = QByteArray (mid).left( (int)( mid - pos - 1 ) );
+ if ( encoding == 'Q' ) {
+ // decode quoted printable text
+ for ( i = cstr.length () - 1; i >= 0; --i ) {
+ if ( cstr[i] == '_' ) {
+ cstr[i] = ' ';
+ }
+ }
+// kDebug() << "before QP '"
+// << cstr << "'";
+ cstr = KCodecs::quotedPrintableDecode( cstr );
+// kDebug() << "after QP '"
+// << cstr << "'";
+ } else {
+ // decode base64 text
+ cstr = QByteArray::fromBase64( cstr );
+ }
+ *pos = ch;
+ int len = cstr.length();
+ for ( i = 0; i < len; ++i ) {
+ result += cstr[i];
+ }
+
+ pos = end - 1;
+ } else {
+// kDebug() << "invalid";
+ //result += "=?";
+ //pos = beg -1; // because pos gets increased shortly afterwards
+ pos = beg - 2;
+ result += *pos++;
+ result += *pos;
+ }
+ }
+ if ( !charset.isEmpty () ) {
+ QTextCodec *aCodec = codecForName( charset.toAscii () );
+ if ( aCodec ) {
+// kDebug() << "Codec is" << aCodec->name();
+ return aCodec->toUnicode( result );
+ }
+ }
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+const QString KIMAP::encodeRFC2047String( const QString &str )
+{
+ return encodeRFC2047String( str.toLatin1() );
+}
+
+//-----------------------------------------------------------------------------
+const QByteArray KIMAP::encodeRFC2047String( const QByteArray &str )
+{
+ if ( str.isEmpty () ) {
+ return str;
+ }
+
+ const signed char *latin =
+ reinterpret_cast<const signed char *>
+ ( str.data() ), *l, *start, *stop;
+ char hexcode;
+ int numQuotes, i;
+ int rptr = 0;
+ // My stats show this number results in 12 resize() out of 73,000
+ int resultLen = 3 * str.length() / 2;
+ QByteArray result( resultLen, '\0' );
+
+ while ( *latin ) {
+ l = latin;
+ start = latin;
+ while ( *l ) {
+ if ( *l == 32 ) {
+ start = l + 1;
+ }
+ if ( *l < 0 ) {
+ break;
+ }
+ l++;
+ }
+ if ( *l ) {
+ numQuotes = 1;
+ while ( *l ) {
+ /* The encoded word must be limited to 75 character */
+ for ( i = 0; i < 16; ++i ) {
+ if ( *l == especials[i] ) {
+ numQuotes++;
+ }
+ }
+ if ( *l < 0 ) {
+ numQuotes++;
+ }
+ /* Stop after 58 = 75 - 17 characters or at "<user@host..." */
+ if ( l - start + 2 * numQuotes >= 58 || *l == 60 ) {
+ break;
+ }
+ l++;
+ }
+ if ( *l ) {
+ stop = l - 1;
+ while ( stop >= start && *stop != 32 ) {
+ stop--;
+ }
+ if ( stop <= start ) {
+ stop = l;
+ }
+ } else {
+ stop = l;
+ }
+ if ( resultLen - rptr - 1 <= start - latin + 1 + 16 ) {
+ // =?iso-88...
+ resultLen += ( start - latin + 1 ) * 2 + 20; // more space
+ result.resize( resultLen );
+ }
+ while ( latin < start ) {
+ result[rptr++] = *latin;
+ latin++;
+ }
+ result.replace( rptr, 15, "=?iso-8859-1?q?" );
+ rptr += 15;
+ if ( resultLen - rptr - 1 <= 3 * ( stop - latin + 1 ) ) {
+ resultLen += ( stop - latin + 1 ) * 4 + 20; // more space
+ result.resize( resultLen );
+ }
+ while ( latin < stop ) {
+ // can add up to 3 chars/iteration
+ numQuotes = 0;
+ for ( i = 0; i < 16; ++i ) {
+ if ( *latin == especials[i] ) {
+ numQuotes = 1;
+ }
+ }
+ if ( *latin < 0 ) {
+ numQuotes = 1;
+ }
+ if ( numQuotes ) {
+ result[rptr++] = '=';
+ hexcode = ( ( *latin & 0xF0 ) >> 4 ) + 48;
+ if ( hexcode >= 58 ) {
+ hexcode += 7;
+ }
+ result[rptr++] = hexcode;
+ hexcode = ( *latin & 0x0F ) + 48;
+ if ( hexcode >= 58 ) {
+ hexcode += 7;
+ }
+ result[rptr++] = hexcode;
+ } else {
+ result[rptr++] = *latin;
+ }
+ latin++;
+ }
+ result[rptr++] = '?';
+ result[rptr++] = '=';
+ } else {
+ while ( *latin ) {
+ if ( rptr == resultLen - 1 ) {
+ resultLen += 30;
+ result.resize( resultLen );
+ }
+ result[rptr++] = *latin;
+ latin++;
+ }
+ }
+ }
+ result[rptr] = 0;
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+const QString KIMAP::encodeRFC2231String( const QString &str )
+{
+ if ( str.isEmpty () ) {
+ return str;
+ }
+
+ signed char *latin = (signed char *)calloc( 1, str.length () + 1 );
+ char *latin_us = (char *)latin;
+ strcpy( latin_us, str.toLatin1 () );
+ signed char *l = latin;
+ char hexcode;
+ int i;
+ bool quote;
+ while ( *l ) {
+ if ( *l < 0 ) {
+ break;
+ }
+ l++;
+ }
+ if ( !*l ) {
+ free( latin );
+ return str;
+ }
+ QByteArray result;
+ l = latin;
+ while ( *l ) {
+ quote = *l < 0;
+ for ( i = 0; i < 16; ++i ) {
+ if ( *l == especials[i] ) {
+ quote = true;
+ }
+ }
+ if ( quote ) {
+ result += '%';
+ hexcode = ( ( *l & 0xF0 ) >> 4 ) + 48;
+ if ( hexcode >= 58 ) {
+ hexcode += 7;
+ }
+ result += hexcode;
+ hexcode = ( *l & 0x0F ) + 48;
+ if ( hexcode >= 58 ) {
+ hexcode += 7;
+ }
+ result += hexcode;
+ } else {
+ result += *l;
+ }
+ l++;
+ }
+ free( latin );
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+const QString KIMAP::decodeRFC2231String( const QString &str )
+{
+ int p = str.indexOf ( '\'' );
+
+ //see if it is an rfc string
+ if ( p < 0 ) {
+ return str;
+ }
+
+ int l = str.lastIndexOf( '\'' );
+
+ //second is language
+ if ( p >= l ) {
+ return str;
+ }
+
+ //first is charset or empty
+ QString charset = str.left ( p );
+ QString st = str.mid ( l + 1 );
+ QString language = str.mid ( p + 1, l - p - 1 );
+
+ //kDebug() << "Charset:" << charset << "Language:" << language;
+
+ char ch, ch2;
+ p = 0;
+ while ( p < (int) st.length () ) {
+ if ( st.at( p ) == 37 ) {
+ ch = st.at( p + 1 ).toLatin1 () - 48;
+ if ( ch > 16 ) {
+ ch -= 7;
+ }
+ ch2 = st.at( p + 2 ).toLatin1 () - 48;
+ if ( ch2 > 16 ) {
+ ch2 -= 7;
+ }
+ st.replace( p, 1, ch * 16 + ch2 );
+ st.remove ( p + 1, 2 );
+ }
+ p++;
+ }
+ return st;
+}
diff --git a/kimap/rfccodecs.h b/kimap/rfccodecs.h
new file mode 100644
index 0000000..922189c
--- /dev/null
+++ b/kimap/rfccodecs.h
@@ -0,0 +1,139 @@
+/**********************************************************************
+ *
+ * rfccodecs - handler for various rfc/mime encodings
+ * Copyright (C) 2000 s.carstens@gmx.de
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ *********************************************************************/
+/**
+ * @file
+ * This file is part of the IMAP support library and defines the
+ * RfcCodecs class.
+ *
+ * @brief
+ * Provides handlers for various RFC/MIME encodings.
+ *
+ * @author Sven Carstens
+ */
+
+#ifndef KIMAP_RFCCODECS_H
+#define KIMAP_RFCCODECS_H
+
+#include <QtCore/QString>
+
+#include "kimap_export.h"
+
+class QTextCodec;
+
+namespace KIMAP {
+
+ /**
+ Converts an Unicode IMAP mailbox to a QByteArray which can be used in
+ IMAP communication.
+ @param src is the QByteArray containing the IMAP mailbox.
+ @since 4.3
+ */
+ KIMAP_EXPORT QByteArray encodeImapFolderName( const QByteArray &src );
+
+ /**
+ Converts an UTF-7 encoded IMAP mailbox to a QByteArray
+ @param inSrc is the QByteArray containing the Unicode path.
+ @since 4.3
+ */
+ KIMAP_EXPORT QByteArray decodeImapFolderName( const QByteArray &inSrc );
+ /**
+ Converts an Unicode IMAP mailbox to a QString which can be used in
+ IMAP communication.
+ @param src is the QString containing the IMAP mailbox.
+ */
+ KIMAP_EXPORT QString encodeImapFolderName( const QString &src );
+
+ /**
+ Converts an UTF-7 encoded IMAP mailbox to a Unicode QString.
+ @param inSrc is the QString containing the Unicode path.
+ */
+ KIMAP_EXPORT QString decodeImapFolderName( const QString &inSrc );
+
+ /**
+ Replaces " with \" and \ with \\ " and \ characters.
+ @param src is the QString to quote.
+ */
+ KIMAP_EXPORT QString quoteIMAP( const QString &src );
+
+ /**
+ Replaces " with \" and \ with \\ " and \ characters.
+ @param src is the QString to quote.
+ @since 4.3
+ */
+ KIMAP_EXPORT QByteArray quoteIMAP( const QByteArray &src );
+
+ /**
+ Fetches a Codec by @p name.
+ @param name is the QString version of the Codec name.
+ @return Text Codec object
+ */
+ KIMAP_EXPORT QTextCodec *codecForName( const QString &name );
+
+ /**
+ Decodes a RFC2047 string @p str.
+ @param str is the QString to decode.
+ @param charset is the character set to use when decoding.
+ @param language is the language found in the charset.
+ */
+ KIMAP_EXPORT const QString decodeRFC2047String( const QString &str,
+ QString &charset,
+ QString &language );
+ /**
+ Decodes a RFC2047 string @p str.
+ @param str is the QString to decode.
+ @param charset is the character set to use when decoding.
+ */
+ KIMAP_EXPORT const QString decodeRFC2047String( const QString &str,
+ QString &charset );
+
+ /**
+ Decodes a RFC2047 string @p str.
+ @param str is the QString to decode.
+ */
+ KIMAP_EXPORT const QString decodeRFC2047String( const QString &str );
+
+ /**
+ Encodes a RFC2047 string @p str.
+ @param str is the QString to encode.
+ */
+ KIMAP_EXPORT const QString encodeRFC2047String( const QString &str );
+
+ /**
+ Encodes a RFC2047 string @p str.
+ @param str is the QString to encode.
+ */
+ KIMAP_EXPORT const QByteArray encodeRFC2047String( const QByteArray &str );
+
+ /**
+ Encodes a RFC2231 string @p str.
+ @param str is the QString to encode.
+ */
+ KIMAP_EXPORT const QString encodeRFC2231String( const QString &str );
+
+ /**
+ Decodes a RFC2231 string @p str.
+ @param str is the QString to decode.
+ */
+ KIMAP_EXPORT const QString decodeRFC2231String( const QString &str );
+}
+
+#endif
diff --git a/kimap/searchjob.cpp b/kimap/searchjob.cpp
new file mode 100644
index 0000000..1e6e27f
--- /dev/null
+++ b/kimap/searchjob.cpp
@@ -0,0 +1,316 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "searchjob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include <QtCore/QDate>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+//TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response
+
+namespace KIMAP
+{
+ class SearchJobPrivate : public JobPrivate
+ {
+ public:
+ SearchJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name), logic(SearchJob::And) {
+ criteriaMap[SearchJob::All] = "ALL";
+ criteriaMap[SearchJob::Answered] = "ANSWERED";
+ criteriaMap[SearchJob::BCC] = "BCC";
+ criteriaMap[SearchJob::Before] = "BEFORE";
+ criteriaMap[SearchJob::Body] = "BODY";
+ criteriaMap[SearchJob::CC] = "CC";
+ criteriaMap[SearchJob::Deleted] = "DELETED";
+ criteriaMap[SearchJob::Draft] = "DRAFT";
+ criteriaMap[SearchJob::Flagged] = "FLAGGED";
+ criteriaMap[SearchJob::From] = "FROM";
+ criteriaMap[SearchJob::Header] = "HEADER";
+ criteriaMap[SearchJob::Keyword] = "KEYWORD";
+ criteriaMap[SearchJob::Larger] = "LARGER";
+ criteriaMap[SearchJob::New] = "NEW";
+ criteriaMap[SearchJob::Old] = "OLD";
+ criteriaMap[SearchJob::On] = "ON";
+ criteriaMap[SearchJob::Recent] = "RECENT";
+ criteriaMap[SearchJob::Seen] = "SEEN";
+ criteriaMap[SearchJob::SentBefore] = "SENTBEFORE";
+ criteriaMap[SearchJob::SentOn] = "SENTON";
+ criteriaMap[SearchJob::SentSince] = "SENTSINCE";
+ criteriaMap[SearchJob::Since] = "SINCE";
+ criteriaMap[SearchJob::Smaller] = "SMALLER";
+ criteriaMap[SearchJob::Subject] = "SUBJECT";
+ criteriaMap[SearchJob::Text] = "TEXT";
+ criteriaMap[SearchJob::To] = "TO";
+ criteriaMap[SearchJob::Uid] = "UID";
+ criteriaMap[SearchJob::Unanswered] = "UNANSWERED";
+ criteriaMap[SearchJob::Undeleted] = "UNDELETED";
+ criteriaMap[SearchJob::Undraft] = "UNDRAFT";
+ criteriaMap[SearchJob::Unflagged] = "UNFLAGGED";
+ criteriaMap[SearchJob::Unkeyword] = "UNKEYWORD";
+ criteriaMap[SearchJob::Unseen] = "UNSEEN";
+
+ //don't use QDate::shortMonthName(), it returns a localized month name
+ months[1] = "Jan";
+ months[2] = "Feb";
+ months[3] = "Mar";
+ months[4] = "Apr";
+ months[5] = "May";
+ months[6] = "Jun";
+ months[7] = "Jul";
+ months[8] = "Aug";
+ months[9] = "Sep";
+ months[10] = "Oct";
+ months[11] = "Nov";
+ months[12] = "Dec";
+
+ nextContent = 0;
+ uidBased = false;
+ }
+ ~SearchJobPrivate() { }
+
+
+ QByteArray charset;
+ QList<QByteArray> criterias;
+ QMap<SearchJob::SearchCriteria, QByteArray > criteriaMap;
+ QMap<int, QByteArray> months;
+ SearchJob::SearchLogic logic;
+ QList<QByteArray> contents;
+ QList<qint64> results;
+ uint nextContent;
+ bool uidBased;
+ };
+}
+
+using namespace KIMAP;
+
+SearchJob::SearchJob( Session *session )
+ : Job( *new SearchJobPrivate(session, i18nc("Name of the search job", "Search")) )
+{
+}
+
+SearchJob::~SearchJob()
+{
+}
+
+void SearchJob::doStart()
+{
+ Q_D(SearchJob);
+
+ QByteArray searchKey;
+
+ if (!d->charset.isEmpty()) {
+ searchKey = "CHARSET " + d->charset;
+ }
+
+ if (d->logic == SearchJob::Not) {
+ searchKey += "NOT";
+ } else if (d->logic == SearchJob::Or) {
+ searchKey += "OR";
+ }
+
+ if ( d->logic == SearchJob::And ) {
+ for ( int i = 0; i<d->criterias.size(); i++ ) {
+ const QByteArray key = d->criterias.at( i );
+ if ( i>0 ) searchKey+= ' ';
+ searchKey += key;
+ }
+ } else {
+ for ( int i = 0; i<d->criterias.size(); i++ ) {
+ const QByteArray key = d->criterias.at( i );
+ if ( i>0 ) searchKey+= ' ';
+ searchKey += '(' + key + ')';
+ }
+ }
+
+ QByteArray command = "SEARCH";
+ if ( d->uidBased ) {
+ command = "UID "+ command;
+ }
+
+ d->tags << d->sessionInternal()->sendCommand( command, searchKey );
+}
+
+void SearchJob::handleResponse( const Message &response )
+{
+ Q_D(SearchJob);
+
+ if (handleErrorReplies(response) == NotHandled ) {
+ if ( response.content[0].toString() == "+" ) {
+ d->sessionInternal()->sendData( d->contents[d->nextContent] );
+ d->nextContent++;
+ } else if ( response.content[1].toString() == "SEARCH" ) {
+ for(int i = 2; i < response.content.size(); i++) {
+ d->results.append(response.content[i].toString().toInt());
+ }
+ }
+ }
+}
+
+
+void SearchJob::setCharset( const QByteArray &charset )
+{
+ Q_D(SearchJob);
+ d->charset = charset;
+}
+
+QByteArray SearchJob::charset() const
+{
+ Q_D(const SearchJob);
+ return d->charset;
+}
+
+void SearchJob::setSearchLogic( SearchLogic logic )
+{
+ Q_D(SearchJob);
+ d->logic = logic;
+}
+
+void SearchJob::addSearchCriteria( SearchCriteria criteria )
+{
+ Q_D(SearchJob);
+
+ switch (criteria) {
+ case All:
+ case Answered:
+ case Deleted:
+ case Draft:
+ case Flagged:
+ case New:
+ case Old:
+ case Recent:
+ case Seen:
+ case Unanswered:
+ case Undeleted:
+ case Undraft:
+ case Unflagged:
+ case Unseen:
+ d->criterias.append(d->criteriaMap[criteria]);
+ break;
+ default:
+ //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
+ kDebug() << "Criteria " << d->criteriaMap[criteria] << " needs an argument, but none was specified.";
+ break;
+ }
+}
+
+
+void SearchJob::addSearchCriteria( SearchCriteria criteria, int argument )
+{
+ Q_D(SearchJob);
+ switch (criteria) {
+ case Larger:
+ case Smaller:
+ d->criterias.append(d->criteriaMap[criteria] + ' ' + QByteArray::number(argument));
+ break;
+ default:
+ //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
+ kDebug() << "Criteria " << d->criteriaMap[criteria] << " doesn't accept an integer as an argument.";
+ break;
+ }
+}
+
+
+void SearchJob::addSearchCriteria( SearchCriteria criteria, const QByteArray &argument )
+{
+ Q_D(SearchJob);
+ switch (criteria) {
+ case BCC:
+ case Body:
+ case CC:
+ case From:
+ case Subject:
+ case Text:
+ case To:
+ d->contents.append(argument);
+ d->criterias.append(d->criteriaMap[criteria] + " {" + QByteArray::number(argument.size()) + '}');
+ break;
+ case Keyword:
+ case Unkeyword:
+ case Header:
+ case Uid:
+ d->criterias.append(d->criteriaMap[criteria] + ' ' + argument);
+ break;
+ default:
+ //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
+ kDebug() << "Criteria " << d->criteriaMap[criteria] << " doesn't accept any argument.";
+ break;
+ }
+}
+
+void SearchJob::addSearchCriteria( SearchCriteria criteria, const QDate &argument )
+{
+ Q_D(SearchJob);
+ switch (criteria) {
+ case Before:
+ case On:
+ case SentBefore:
+ case SentSince:
+ case Since: {
+ QByteArray date = QByteArray::number(argument.day()) + '-';
+ date += d->months[argument.month()] + '-';
+ date += QByteArray::number(argument.year());
+ d->criterias.append(d->criteriaMap[criteria] + " \"" + date + '\"');
+ break;
+ }
+ default:
+ //TODO Discuss if we keep error checking here, or accept anything, even if it is wrong
+ kDebug() << "Criteria " << d->criteriaMap[criteria] << " doesn't accept a date as argument.";
+ break;
+ }
+}
+
+void SearchJob::addSearchCriteria( const QByteArray &searchCriteria )
+{
+ Q_D(SearchJob);
+ d->criterias.append(searchCriteria);
+}
+
+void SearchJob::setUidBased(bool uidBased)
+{
+ Q_D(SearchJob);
+ d->uidBased = uidBased;
+}
+
+bool SearchJob::isUidBased() const
+{
+ Q_D(const SearchJob);
+ return d->uidBased;
+}
+
+QList<qint64> SearchJob::results() const
+{
+ Q_D(const SearchJob);
+ return d->results;
+}
+
+QList<int> SearchJob::foundItems()
+{
+ Q_D(const SearchJob);
+
+ QList<int> results;
+ qCopy( d->results.begin(), d->results.end(), results.begin() );
+
+ return results;
+}
+
diff --git a/kimap/searchjob.h b/kimap/searchjob.h
new file mode 100644
index 0000000..077e8d4
--- /dev/null
+++ b/kimap/searchjob.h
@@ -0,0 +1,162 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SEARCHJOB_H
+#define KIMAP_SEARCHJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+class QDate;
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class SearchJobPrivate;
+
+class KIMAP_EXPORT SearchJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(SearchJob)
+
+ friend class SessionPrivate;
+
+ public:
+ enum SearchLogic {
+ And = 0,
+ Or,
+ Not
+ };
+
+ enum SearchCriteria {
+ All = 0,
+ Answered,
+ BCC,
+ Before,
+ Body,
+ CC,
+ Deleted,
+ Draft,
+ Flagged,
+ From,
+ Header,
+ Keyword,
+ Larger,
+ New,
+ Old,
+ On,
+ Recent,
+ Seen,
+ SentBefore,
+ SentOn,
+ SentSince,
+ Since,
+ Smaller,
+ Subject,
+ Text,
+ To,
+ Uid,
+ Unanswered,
+ Undeleted,
+ Undraft,
+ Unflagged,
+ Unkeyword,
+ Unseen
+ };
+
+ explicit SearchJob( Session *session );
+ virtual ~SearchJob();
+
+ void setUidBased(bool uidBased);
+ bool isUidBased() const;
+
+ void setCharset( const QByteArray &charSet );
+ QByteArray charset() const;
+
+ /**
+ * Get the search result, as a list of sequence numbers or UIDs, based on the isUidBased status
+ * @return the found items
+ * @deprecated use results() instead
+ */
+ KDE_DEPRECATED QList<int> foundItems();
+
+ /**
+ * Get the search result, as a list of sequence numbers or UIDs, based on the isUidBased status
+ * @return the found items
+ * @since 4.6
+ */
+ QList<qint64> results() const;
+
+ /**
+ * Add a search criteria that doesn't have an argument. Passing a criteria that
+ * should have an argument will be ignored.
+ * @param criteria a criteria from SearchCriterias
+ */
+ void addSearchCriteria( SearchCriteria criteria );
+
+ /**
+ * Add a search criteria that has one or more space separate string arguments.
+ * Passing a criteria that accepts a different type or argument or no
+ * argument will be ignored.
+ * @param criteria a criteria from SearchCriterias
+ * @param argument the arguments
+ */
+ void addSearchCriteria( SearchCriteria criteria, const QByteArray &argument );
+
+ /**
+ * Add a search criteria that has an integer argument.
+ * Passing a criteria that accepts a different type or argument or no
+ * argument will be ignored.
+ * @param criteria a criteria from SearchCriterias
+ * @param argument a number argument
+ */
+ void addSearchCriteria( SearchCriteria criteria, int argument );
+
+ /**
+ * Add a search criteria that has a date as argument.
+ * Passing a criteria that accepts a different type or argument or no
+ * argument will be ignored.
+ * @param criteria a criteria from SearchCriterias
+ * @param argument a date
+ */
+ void addSearchCriteria( SearchCriteria criteria, const QDate& argument );
+
+ /**
+ * Add a custom criteria. No checks are done, the data is sent as it is
+ * to the server.
+ * @param searchCriteria free form search criteria.
+ */
+ void addSearchCriteria( const QByteArray &searchCriteria );
+
+ /**
+ * Set the logic combining the search criterias.
+ * @param logic AND (the default), OR, NOT. See SearchLogics.
+ */
+ void setSearchLogic(SearchLogic logic);
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+};
+
+}
+
+#endif
diff --git a/kimap/selectjob.cpp b/kimap/selectjob.cpp
new file mode 100644
index 0000000..fab1c53
--- /dev/null
+++ b/kimap/selectjob.cpp
@@ -0,0 +1,194 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "selectjob.h"
+
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class SelectJobPrivate : public JobPrivate
+ {
+ public:
+ SelectJobPrivate( Session *session, const QString& name )
+ : JobPrivate(session, name), readOnly(false), messageCount(-1), recentCount(-1),
+ firstUnseenIndex(-1), uidValidity(-1), nextUid(-1) { }
+ ~SelectJobPrivate() { }
+
+ QString mailBox;
+ bool readOnly;
+
+ QList<QByteArray> flags;
+ QList<QByteArray> permanentFlags;
+ int messageCount;
+ int recentCount;
+ int firstUnseenIndex;
+ qint64 uidValidity;
+ qint64 nextUid;
+ };
+}
+
+using namespace KIMAP;
+
+SelectJob::SelectJob( Session *session )
+ : Job( *new SelectJobPrivate(session, i18nc("name of the select job", "Select")) )
+{
+}
+
+SelectJob::~SelectJob()
+{
+}
+
+void SelectJob::setMailBox( const QString &mailBox )
+{
+ Q_D(SelectJob);
+ d->mailBox = mailBox;
+}
+
+QString SelectJob::mailBox() const
+{
+ Q_D(const SelectJob);
+ return d->mailBox;
+}
+
+void SelectJob::setOpenReadOnly( bool readOnly )
+{
+ Q_D(SelectJob);
+ d->readOnly = readOnly;
+}
+
+bool SelectJob::isOpenReadOnly() const
+{
+ Q_D(const SelectJob);
+ return d->readOnly;
+}
+
+QList<QByteArray> SelectJob::flags() const
+{
+ Q_D(const SelectJob);
+ return d->flags;
+}
+
+QList<QByteArray> SelectJob::permanentFlags() const
+{
+ Q_D(const SelectJob);
+ return d->permanentFlags;
+}
+
+int SelectJob::messageCount() const
+{
+ Q_D(const SelectJob);
+ return d->messageCount;
+}
+
+int SelectJob::recentCount() const
+{
+ Q_D(const SelectJob);
+ return d->recentCount;
+}
+
+int SelectJob::firstUnseenIndex() const
+{
+ Q_D(const SelectJob);
+ return d->firstUnseenIndex;
+}
+
+qint64 SelectJob::uidValidity() const
+{
+ Q_D(const SelectJob);
+ return d->uidValidity;
+}
+
+qint64 SelectJob::nextUid() const
+{
+ Q_D(const SelectJob);
+ return d->nextUid;
+}
+
+void SelectJob::doStart()
+{
+ Q_D(SelectJob);
+
+ QByteArray command = "SELECT";
+ if ( d->readOnly ) {
+ command = "EXAMINE";
+ }
+
+ d->tags << d->sessionInternal()->sendCommand( command, '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"' );
+}
+
+void SelectJob::handleResponse( const Message &response )
+{
+ Q_D(SelectJob);
+
+ if ( handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 2 ) {
+ QByteArray code = response.content[1].toString();
+
+ if ( code=="OK" ) {
+ if ( response.responseCode.size() < 2 ) return;
+
+ code = response.responseCode[0].toString();
+
+ if ( code=="PERMANENTFLAGS" ) {
+ d->permanentFlags = response.responseCode[1].toList();
+ } else {
+ bool isInt;
+
+ if ( code=="UIDVALIDITY" ) {
+ qint64 value = response.responseCode[1].toString().toLongLong(&isInt);
+ if ( !isInt ) return;
+ d->uidValidity = value;
+ } else {
+ qint64 value = response.responseCode[1].toString().toLongLong(&isInt);
+ if ( !isInt ) return;
+ if ( code=="UNSEEN" ) {
+ d->firstUnseenIndex = value;
+ } else if ( code=="UIDNEXT" ) {
+ d->nextUid = value;
+ }
+ }
+ }
+ } else if ( code=="FLAGS" ) {
+ d->flags = response.content[2].toList();
+ } else {
+ bool isInt;
+ int value = response.content[1].toString().toInt(&isInt);
+ if ( !isInt || response.content.size()<3 ) return;
+
+ code = response.content[2].toString();
+ if ( code=="EXISTS" ) {
+ d->messageCount = value;
+ } else if ( code=="RECENT" ) {
+ d->recentCount = value;
+ }
+ }
+ } else {
+ qDebug("%s", response.toString().constData());
+ }
+ } else {
+ Q_ASSERT( error() || d->m_session->selectedMailBox() == d->mailBox );
+ }
+}
+
diff --git a/kimap/selectjob.h b/kimap/selectjob.h
new file mode 100644
index 0000000..c67d28c
--- /dev/null
+++ b/kimap/selectjob.h
@@ -0,0 +1,67 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SELECTJOB_H
+#define KIMAP_SELECTJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class SelectJobPrivate;
+
+class KIMAP_EXPORT SelectJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(SelectJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit SelectJob( Session *session );
+ virtual ~SelectJob();
+
+ void setMailBox( const QString &mailBox );
+ QString mailBox() const;
+
+ void setOpenReadOnly( bool readOnly );
+ bool isOpenReadOnly() const;
+
+ QList<QByteArray> flags() const;
+ QList<QByteArray> permanentFlags() const;
+
+ int messageCount() const;
+ int recentCount() const;
+ int firstUnseenIndex() const;
+
+ qint64 uidValidity() const;
+ qint64 nextUid() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse( const Message &response );
+};
+
+}
+
+#endif
diff --git a/kimap/session.cpp b/kimap/session.cpp
new file mode 100644
index 0000000..de174d6
--- /dev/null
+++ b/kimap/session.cpp
@@ -0,0 +1,497 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "session.h"
+#include "session_p.h"
+#include "sessionuiproxy.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+#include <KDebug>
+#include <KDE/KLocale>
+
+#include "job.h"
+#include "loginjob.h"
+#include "message_p.h"
+#include "sessionlogger_p.h"
+#include "sessionthread_p.h"
+#include "rfccodecs.h"
+
+Q_DECLARE_METATYPE(KTcpSocket::SslVersion)
+Q_DECLARE_METATYPE(QSslSocket::SslMode)
+static const int _kimap_sslVersionId = qRegisterMetaType<KTcpSocket::SslVersion>();
+
+using namespace KIMAP;
+
+Session::Session( const QString &hostName, quint16 port, QObject *parent)
+ : QObject(parent), d(new SessionPrivate(this))
+{
+ if ( !qgetenv( "KIMAP_LOGFILE" ).isEmpty() ) {
+ d->logger = new SessionLogger;
+ }
+
+ d->isSocketConnected = false;
+ d->state = Disconnected;
+ d->jobRunning = false;
+
+ d->thread = new SessionThread(hostName, port, this);
+ connect(d->thread, SIGNAL(encryptionNegotiationResult(bool,KTcpSocket::SslVersion)),
+ d, SLOT(onEncryptionNegotiationResult(bool,KTcpSocket::SslVersion)));
+ connect(d->thread, SIGNAL(sslError(KSslErrorUiData)), this, SLOT(handleSslError(KSslErrorUiData)));
+
+ d->startSocketTimer();
+ d->thread->start();
+}
+
+Session::~Session()
+{
+ delete d->thread;
+}
+
+void Session::setUiProxy(SessionUiProxy::Ptr proxy)
+{
+ d->uiProxy = proxy;
+}
+
+void Session::setUiProxy(SessionUiProxy *proxy)
+{
+ setUiProxy( SessionUiProxy::Ptr( proxy ) );
+}
+
+QString Session::hostName() const
+{
+ return d->thread->hostName();
+}
+
+quint16 Session::port() const
+{
+ return d->thread->port();
+}
+
+Session::State Session::state() const
+{
+ return d->state;
+}
+
+QString Session::userName() const
+{
+ return d->userName;
+}
+
+QByteArray Session::serverGreeting() const
+{
+ return d->greeting;
+}
+
+int Session::jobQueueSize() const
+{
+ return d->queue.size() + ( d->jobRunning ? 1 : 0 );
+}
+
+void KIMAP::Session::close()
+{
+ d->thread->closeSocket();
+}
+
+void SessionPrivate::handleSslError(const KSslErrorUiData& errorData)
+{
+ if (uiProxy && uiProxy->ignoreSslError(errorData)) {
+ QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, true) );
+ } else {
+ QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, false) );
+ }
+}
+
+SessionPrivate::SessionPrivate( Session *session )
+ : QObject( session ),
+ q(session),
+ state(Session::Disconnected),
+ logger(0),
+ currentJob(0),
+ tagCount(0),
+ sslVersion(KTcpSocket::UnknownSslVersion),
+ socketTimerInterval(30000) // By default timeouts on 30s
+{
+}
+
+SessionPrivate::~SessionPrivate()
+{
+ delete logger;
+}
+
+void SessionPrivate::addJob(Job *job)
+{
+ queue.append(job);
+ emit q->jobQueueSizeChanged( q->jobQueueSize() );
+
+ QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(jobDone(KJob*)) );
+ QObject::connect( job, SIGNAL(destroyed(QObject*)), q, SLOT(jobDestroyed(QObject*)) );
+
+ if ( state!=Session::Disconnected ) {
+ startNext();
+ }
+}
+
+void SessionPrivate::startNext()
+{
+ QTimer::singleShot( 0, q, SLOT(doStartNext()) );
+}
+
+void SessionPrivate::doStartNext()
+{
+ if ( queue.isEmpty() || jobRunning || !isSocketConnected ) {
+ return;
+ }
+
+ startSocketTimer();
+ jobRunning = true;
+
+ currentJob = queue.dequeue();
+ currentJob->doStart();
+}
+
+void SessionPrivate::jobDone( KJob *job )
+{
+ Q_UNUSED( job );
+ Q_ASSERT( job == currentJob );
+
+ // If we're in disconnected state it's because we ended up
+ // here because the inactivity timer triggered, so no need to
+ // stop it (it is single shot)
+ if ( state!=Session::Disconnected ) {
+ stopSocketTimer();
+ }
+
+ jobRunning = false;
+ currentJob = 0;
+ emit q->jobQueueSizeChanged( q->jobQueueSize() );
+ startNext();
+}
+
+void SessionPrivate::jobDestroyed( QObject *job )
+{
+ queue.removeAll( static_cast<KIMAP::Job*>( job ) );
+ if ( currentJob == job )
+ currentJob = 0;
+}
+
+void SessionPrivate::responseReceived( const Message &response )
+{
+ if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) {
+ logger->dataReceived( response.toString() );
+ }
+
+ QByteArray tag;
+ QByteArray code;
+
+ if ( response.content.size()>=1 ) {
+ tag = response.content[0].toString();
+ }
+
+ if ( response.content.size()>=2 ) {
+ code = response.content[1].toString();
+ }
+
+ switch ( state ) {
+ case Session::Disconnected:
+ if (socketTimer.isActive()) {
+ stopSocketTimer();
+ }
+ if ( code=="OK" ) {
+ setState(Session::NotAuthenticated);
+
+ Message simplified = response;
+ simplified.content.removeFirst(); // Strip the tag
+ simplified.content.removeFirst(); // Strip the code
+ greeting = simplified.toString().trimmed(); // Save the server greeting
+
+ startNext();
+ } else if ( code=="PREAUTH" ) {
+ setState(Session::Authenticated);
+
+ Message simplified = response;
+ simplified.content.removeFirst(); // Strip the tag
+ simplified.content.removeFirst(); // Strip the code
+ greeting = simplified.toString().trimmed(); // Save the server greeting
+
+ startNext();
+ } else {
+ thread->closeSocket();
+ }
+ return;
+ case Session::NotAuthenticated:
+ if ( code=="OK" && tag==authTag ) {
+ setState(Session::Authenticated);
+ }
+ break;
+ case Session::Authenticated:
+ if ( code=="OK" && tag==selectTag ) {
+ setState(Session::Selected);
+ currentMailBox = upcomingMailBox;
+ }
+ break;
+ case Session::Selected:
+ if ( ( code=="OK" && tag==closeTag )
+ || ( code!="OK" && tag==selectTag) ) {
+ setState(Session::Authenticated);
+ currentMailBox = QByteArray();
+ } else if ( code=="OK" && tag==selectTag ) {
+ currentMailBox = upcomingMailBox;
+ }
+ break;
+ }
+
+ if (tag==authTag) authTag.clear();
+ if (tag==selectTag) selectTag.clear();
+ if (tag==closeTag) closeTag.clear();
+
+ // If a job is running forward it the response
+ if ( currentJob!=0 ) {
+ restartSocketTimer();
+ currentJob->handleResponse( response );
+ } else {
+ qWarning() << "A message was received from the server with no job to handle it:"
+ << response.toString()
+ << '('+response.toString().toHex()+')';
+ }
+}
+
+void SessionPrivate::setState(Session::State s)
+{
+ if (s != state) {
+ Session::State oldState = state;
+ state = s;
+ emit q->stateChanged(state, oldState);
+ }
+}
+
+QByteArray SessionPrivate::sendCommand( const QByteArray &command, const QByteArray &args )
+{
+ QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0');
+
+ QByteArray payload = tag+' '+command;
+ if ( !args.isEmpty() ) {
+ payload+= ' '+args;
+ }
+
+ sendData( payload );
+
+ if ( command=="LOGIN" || command=="AUTHENTICATE" ) {
+ authTag = tag;
+ } else if ( command=="SELECT" || command=="EXAMINE" ) {
+ selectTag = tag;
+ upcomingMailBox = args;
+ upcomingMailBox.remove( 0, 1 );
+ upcomingMailBox.chop( 1 );
+ upcomingMailBox = KIMAP::decodeImapFolderName( upcomingMailBox );
+ } else if ( command=="CLOSE" ) {
+ closeTag = tag;
+ }
+
+ return tag;
+}
+
+void SessionPrivate::sendData( const QByteArray &data )
+{
+ restartSocketTimer();
+
+ if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) {
+ logger->dataSent( data );
+ }
+
+ thread->sendData(data+"\r\n");
+}
+
+void SessionPrivate::socketConnected()
+{
+ stopSocketTimer();
+ isSocketConnected = true;
+
+ bool willUseSsl = false;
+ if ( !queue.isEmpty() ) {
+ KIMAP::LoginJob *login = qobject_cast<KIMAP::LoginJob*>( queue.first() );
+ if ( login ) {
+ willUseSsl = ( login->encryptionMode() == KIMAP::LoginJob::SslV2 )
+ || ( login->encryptionMode() == KIMAP::LoginJob::SslV3 )
+ || ( login->encryptionMode() == KIMAP::LoginJob::SslV3_1 )
+ || ( login->encryptionMode() == KIMAP::LoginJob::AnySslVersion );
+
+ userName = login->userName();
+ }
+ }
+
+ if ( state == Session::Disconnected && willUseSsl ) {
+ startNext();
+ } else {
+ startSocketTimer();
+ }
+}
+
+void SessionPrivate::socketDisconnected()
+{
+ if (socketTimer.isActive()) {
+ stopSocketTimer();
+ }
+
+ if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) {
+ logger->disconnectionOccured();
+ }
+
+ if ( state != Session::Disconnected ) {
+ setState(Session::Disconnected);
+ emit q->connectionLost();
+ } else {
+ emit q->connectionFailed();
+ }
+
+ isSocketConnected = false;
+
+ clearJobQueue();
+}
+
+void SessionPrivate::socketActivity()
+{
+ restartSocketTimer();
+}
+
+void SessionPrivate::socketError()
+{
+ if (socketTimer.isActive()) {
+ stopSocketTimer();
+ }
+
+ if ( isSocketConnected ) {
+ thread->closeSocket();
+ } else {
+ emit q->connectionFailed();
+ emit q->connectionLost(); // KDE5: Remove this. We shouldn't emit connectionLost() if we weren't connected in the first place
+ clearJobQueue();
+ }
+}
+
+void SessionPrivate::clearJobQueue()
+{
+ if ( currentJob ) {
+ currentJob->connectionLost();
+ } else if ( !queue.isEmpty() ) {
+ currentJob = queue.takeFirst();
+ currentJob->connectionLost();
+ }
+
+ qDeleteAll(queue);
+ queue.clear();
+ emit q->jobQueueSizeChanged( 0 );
+}
+
+void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version)
+{
+ QMetaObject::invokeMethod( thread, "startSsl", Qt::QueuedConnection, Q_ARG(KTcpSocket::SslVersion, version) );
+}
+
+QString Session::selectedMailBox() const
+{
+ return QString::fromUtf8( d->currentMailBox );
+}
+
+void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion version)
+{
+ if ( isEncrypted ) {
+ sslVersion = version;
+ } else {
+ sslVersion = KTcpSocket::UnknownSslVersion;
+ }
+ emit encryptionNegotiationResult( isEncrypted );
+}
+
+KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const
+{
+ return sslVersion;
+}
+
+void SessionPrivate::setSocketTimeout( int ms )
+{
+ bool timerActive = socketTimer.isActive();
+
+ if ( timerActive ) {
+ stopSocketTimer();
+ }
+
+ socketTimerInterval = ms;
+
+ if ( timerActive ) {
+ startSocketTimer();
+ }
+}
+
+int SessionPrivate::socketTimeout() const
+{
+ return socketTimerInterval;
+}
+
+void SessionPrivate::startSocketTimer()
+{
+ if ( socketTimerInterval<0 ) {
+ return;
+ }
+ Q_ASSERT( !socketTimer.isActive() );
+
+ connect( &socketTimer, SIGNAL(timeout()),
+ this, SLOT(onSocketTimeout()) );
+
+ socketTimer.setSingleShot( true );
+ socketTimer.start( socketTimerInterval );
+}
+
+void SessionPrivate::stopSocketTimer()
+{
+ if ( socketTimerInterval<0 ) {
+ return;
+ }
+
+ socketTimer.stop();
+
+ disconnect( &socketTimer, SIGNAL(timeout()),
+ this, SLOT(onSocketTimeout()) );
+}
+
+void SessionPrivate::restartSocketTimer()
+{
+ if ( socketTimer.isActive() ) {
+ stopSocketTimer();
+ }
+ startSocketTimer();
+}
+
+void SessionPrivate::onSocketTimeout()
+{
+ kDebug() << "Socket timeout!";
+ thread->closeSocket();
+}
+
+void Session::setTimeout( int timeout )
+{
+ d->setSocketTimeout( timeout * 1000 );
+}
+
+#include "moc_session.cpp"
+#include "moc_session_p.cpp"
diff --git a/kimap/session.h b/kimap/session.h
new file mode 100644
index 0000000..e23fbc4
--- /dev/null
+++ b/kimap/session.h
@@ -0,0 +1,156 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SESSION_H
+#define KIMAP_SESSION_H
+
+#include "kimap_export.h"
+
+#include <QtCore/QObject>
+
+#include "sessionuiproxy.h"
+
+class KSslErrorUiData;
+
+namespace KIMAP {
+
+class SessionPrivate;
+class JobPrivate;
+struct Message;
+
+class KIMAP_EXPORT Session : public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(State)
+
+ friend class JobPrivate;
+
+ public:
+ enum State { Disconnected = 0, NotAuthenticated, Authenticated, Selected };
+
+ Session( const QString &hostName, quint16 port, QObject *parent=0 );
+ ~Session();
+
+ QString hostName() const;
+ quint16 port() const;
+ State state() const;
+
+ /**
+ * Returns the name that has been set with LoginJob::setUserName()
+ * The user name is useful to uniquely identify an IMAP resource, in combination with the host name
+ * @note If the Session was pre-authenticated, userName() will return an empty string
+ * @since 4.7
+ */
+ QString userName() const;
+
+ QByteArray serverGreeting() const;
+
+ /**
+ * Sets an ui proxy that displays the error messages and waits for user feedback.
+ * @param proxy the ui proxy object
+ */
+ void setUiProxy(SessionUiProxy::Ptr proxy);
+
+ /**
+ * Sets an ui proxy that displays the error messages and waits for user feedback.
+ * @param proxy the ui proxy object
+ * @deprecated Use the shared pointer version instead
+ */
+ KDE_DEPRECATED void setUiProxy(SessionUiProxy *proxy);
+
+ /**
+ * Set the session timeout. The default is 30 seconds.
+ * @param timeout The socket timeout in seconds, negative values disable the timeout.
+ * @since 4.6
+ */
+ void setTimeout( int timeout );
+
+ /**
+ * Returns the currently selected mailbox.
+ * @since 4.5
+ */
+ QString selectedMailBox() const;
+
+ int jobQueueSize() const;
+
+ void close();
+
+ Q_SIGNALS:
+ void jobQueueSizeChanged( int queueSize );
+
+ /**
+ @deprecated
+ Emitted when we loose a previously established connection
+
+ Likely reasons: server closed the connection, loss of internet connectivity, etc...
+
+ For historical reasons, this signal is also emitted in the event of a failed connection, but
+ you should not rely on this behavior.
+
+ New implementations should use connectionFailed() to detect a failure to connect to the host,
+ and stateChanged() to detect a loss of connectivity.
+ */
+ KDE_DEPRECATED void connectionLost();
+
+ /**
+ Emitted when the Session couldn't connect to the host.
+
+ Likely reasons: invalid host address, no internet connectivity, firewall blocking rules,
+ etc...
+
+ Pending jobs in the queue will be deleted, and the first job in the queue will be failed. (ie:
+ it will have its result signal emitted with a non-zero error code.)
+
+ @since 4.7
+ */
+ void connectionFailed();
+
+ /**
+ Emitted when the session's state changes.
+
+ You can use this signal to detect a connection loss (ie: stateChanged is emitted with newState
+ == KIMAP::Session::Disconnected)
+
+ If you want to receive the stateChanged arguments in your slot, you must register the State
+ enum with @c Q_DECLARE_METATYPE(KIMAP::Session::State) and @c qRegisterMetaType<KIMAP::Session::State>();
+
+ @since 4.7
+ */
+ void stateChanged(KIMAP::Session::State newState, KIMAP::Session::State oldState);
+
+ private:
+ Q_PRIVATE_SLOT( d, void doStartNext() )
+ Q_PRIVATE_SLOT( d, void jobDone( KJob* ) )
+ Q_PRIVATE_SLOT( d, void jobDestroyed( QObject* ) )
+ Q_PRIVATE_SLOT( d, void responseReceived( const KIMAP::Message& ) )
+
+ Q_PRIVATE_SLOT( d, void socketConnected() )
+ Q_PRIVATE_SLOT( d, void socketDisconnected() )
+ Q_PRIVATE_SLOT( d, void socketError() )
+ Q_PRIVATE_SLOT( d, void socketActivity() )
+
+ Q_PRIVATE_SLOT( d, void handleSslError( const KSslErrorUiData &errorData ) )
+
+ friend class SessionPrivate;
+ SessionPrivate *const d;
+};
+
+}
+
+#endif
diff --git a/kimap/session_p.h b/kimap/session_p.h
new file mode 100644
index 0000000..37bfd57
--- /dev/null
+++ b/kimap/session_p.h
@@ -0,0 +1,120 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SESSION_P_H
+#define KIMAP_SESSION_P_H
+
+#include "session.h"
+#include "sessionuiproxy.h"
+
+#include <ktcpsocket.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QQueue>
+#include <QtCore/QString>
+#include <QtCore/QTimer>
+
+class KJob;
+
+namespace KIMAP {
+
+class Job;
+struct Message;
+class SessionLogger;
+class SessionThread;
+
+class KIMAP_EXPORT SessionPrivate : public QObject
+{
+ Q_OBJECT
+
+ friend class Session;
+
+ public:
+ SessionPrivate( Session *session );
+ virtual ~SessionPrivate();
+
+ void addJob(Job *job);
+ QByteArray sendCommand( const QByteArray &command, const QByteArray &args = QByteArray() );
+ void startSsl(const KTcpSocket::SslVersion &version);
+ void sendData( const QByteArray &data );
+
+ void handleSslError( const KSslErrorUiData &errorData );
+
+ KTcpSocket::SslVersion negotiatedEncryption() const;
+
+ void setSocketTimeout( int ms );
+ int socketTimeout() const;
+
+ Q_SIGNALS:
+ void encryptionNegotiationResult(bool);
+
+ private Q_SLOTS:
+ void onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion sslVersion);
+ void onSocketTimeout();
+
+ private:
+ void startNext();
+ void doStartNext();
+ void jobDone( KJob *job );
+ void jobDestroyed( QObject *job );
+ void clearJobQueue();
+ void responseReceived( const KIMAP::Message &response );
+ void setState(Session::State state);
+
+ void socketConnected();
+ void socketDisconnected();
+ void socketError();
+ void socketActivity();
+
+ void startSocketTimer();
+ void stopSocketTimer();
+ void restartSocketTimer();
+
+ Session *const q;
+
+ bool isSocketConnected;
+ Session::State state;
+
+ SessionLogger *logger;
+ SessionThread *thread;
+ SessionUiProxy::Ptr uiProxy;
+
+ bool jobRunning;
+ Job *currentJob;
+ QQueue<Job*> queue;
+
+ QByteArray authTag;
+ QByteArray selectTag;
+ QByteArray closeTag;
+
+ QString userName;
+ QByteArray greeting;
+ QByteArray currentMailBox;
+ QByteArray upcomingMailBox;
+ quint16 tagCount;
+
+ KTcpSocket::SslVersion sslVersion;
+
+ int socketTimerInterval;
+ QTimer socketTimer;
+};
+
+}
+
+#endif
diff --git a/kimap/sessionlogger.cpp b/kimap/sessionlogger.cpp
new file mode 100644
index 0000000..715a5da
--- /dev/null
+++ b/kimap/sessionlogger.cpp
@@ -0,0 +1,62 @@
+/*
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "sessionlogger_p.h"
+
+#include <unistd.h>
+
+using namespace KIMAP;
+
+SessionLogger::SessionLogger()
+ : m_id( 0 )
+{
+ static qint64 nextId = 0;
+ m_id = ++nextId;
+
+ m_file.setFileName( qgetenv( "KIMAP_LOGFILE" )
+ + '.' + QString::number( getpid() )
+ + '.' + QString::number( m_id ) );
+ m_file.open( QFile::WriteOnly );
+}
+
+SessionLogger::~SessionLogger()
+{
+ m_file.close();
+}
+
+void SessionLogger::dataSent( const QByteArray &data )
+{
+ m_file.write( "C: "+data.trimmed()+'\n' );
+ m_file.flush();
+}
+
+void SessionLogger::dataReceived( const QByteArray &data )
+{
+ m_file.write( "S: "+data.trimmed()+'\n' );
+ m_file.flush();
+}
+
+void SessionLogger::disconnectionOccured()
+{
+ m_file.write( "X\n" );
+}
+
+
+
diff --git a/kimap/sessionlogger_p.h b/kimap/sessionlogger_p.h
new file mode 100644
index 0000000..91926e5
--- /dev/null
+++ b/kimap/sessionlogger_p.h
@@ -0,0 +1,48 @@
+/*
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SESSIONLOGGER_P_H
+#define KIMAP_SESSIONLOGGER_P_H
+
+#include <QtCore/QObject>
+#include <QtCore/QFile>
+
+namespace KIMAP {
+
+class SessionLoggerPrivate;
+
+class SessionLogger
+{
+ public:
+ SessionLogger();
+ ~SessionLogger();
+
+ void dataSent( const QByteArray &data );
+ void dataReceived( const QByteArray &data );
+ void disconnectionOccured();
+
+ private:
+ qint64 m_id;
+ QFile m_file;
+};
+
+}
+
+#endif
diff --git a/kimap/sessionthread.cpp b/kimap/sessionthread.cpp
new file mode 100644
index 0000000..753fd07
--- /dev/null
+++ b/kimap/sessionthread.cpp
@@ -0,0 +1,232 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "sessionthread_p.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+
+#include <KDE/KDebug>
+
+#include "imapstreamparser.h"
+#include "message_p.h"
+#include "session.h"
+
+using namespace KIMAP;
+
+Q_DECLARE_METATYPE(KTcpSocket::Error)
+Q_DECLARE_METATYPE(KSslErrorUiData)
+static const int _kimap_socketErrorTypeId = qRegisterMetaType<KTcpSocket::Error>();
+static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>();
+
+SessionThread::SessionThread( const QString &hostName, quint16 port, Session *parent )
+ : QThread(), m_hostName(hostName), m_port(port),
+ m_session(parent), m_socket(0), m_stream(0), m_encryptedMode(false)
+{
+ // Yeah, sounds weird, but QThread object is linked to the parent
+ // thread not to itself, and I'm too lazy to introduce yet another
+ // internal QObject
+ moveToThread(this);
+}
+
+SessionThread::~SessionThread()
+{
+ // don't call quit() directly, this will deadlock in wait() if exec() hasn't run yet
+ QMetaObject::invokeMethod( this, "quit" );
+ if ( !wait( 10 * 1000 ) ) {
+ kWarning() << "Session thread refuses to die, killing harder...";
+ terminate();
+ // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called
+ wait();
+ }
+}
+
+void SessionThread::sendData( const QByteArray &payload )
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_dataQueue.enqueue( payload );
+ QTimer::singleShot( 0, this, SLOT(writeDataQueue()) );
+}
+
+void SessionThread::writeDataQueue()
+{
+ QMutexLocker locker(&m_mutex);
+
+ while ( !m_dataQueue.isEmpty() ) {
+ m_socket->write( m_dataQueue.dequeue() );
+ }
+}
+
+void SessionThread::readMessage()
+{
+ QMutexLocker locker(&m_mutex);
+
+ if ( m_stream->availableDataSize()==0 ) {
+ return;
+ }
+
+ Message message;
+ QList<Message::Part> *payload = &message.content;
+
+ try {
+ while ( !m_stream->atCommandEnd() ) {
+ if ( m_stream->hasString() ) {
+ QByteArray string = m_stream->readString();
+ if ( string == "NIL" ) {
+ *payload << Message::Part( QList<QByteArray>() );
+ } else {
+ *payload << Message::Part(string);
+ }
+ } else if ( m_stream->hasList() ) {
+ *payload << Message::Part(m_stream->readParenthesizedList());
+ } else if ( m_stream->hasResponseCode() ) {
+ payload = &message.responseCode;
+ } else if ( m_stream->atResponseCodeEnd() ) {
+ payload = &message.content;
+ } else if ( m_stream->hasLiteral() ) {
+ QByteArray literal;
+ while ( !m_stream->atLiteralEnd() ) {
+ literal+= m_stream->readLiteralPart();
+ }
+ *payload << Message::Part(literal);
+ } else {
+ // Oops! Something really bad happened
+ throw ImapParserException( "Inconsistent state, probably due to some packet loss" );
+ }
+ }
+
+ emit responseReceived(message);
+
+ } catch (KIMAP::ImapParserException e) {
+ qWarning() << "The stream parser raised an exception:" << e.what();
+ }
+
+ if ( m_stream->availableDataSize()>1 ) {
+ QTimer::singleShot( 0, this, SLOT(readMessage()) );
+ }
+
+}
+
+void SessionThread::closeSocket()
+{
+ QTimer::singleShot( 0, this, SLOT(doCloseSocket()) );
+}
+
+void SessionThread::doCloseSocket()
+{
+ m_encryptedMode = false;
+ m_socket->close();
+}
+
+void SessionThread::reconnect()
+{
+ QMutexLocker locker(&m_mutex);
+
+ if ( m_socket->state() != SessionSocket::ConnectedState &&
+ m_socket->state() != SessionSocket::ConnectingState ) {
+ if (m_encryptedMode) {
+ m_socket->connectToHostEncrypted(m_hostName, m_port);
+ } else {
+ m_socket->connectToHost(m_hostName, m_port);
+ }
+ }
+}
+
+void SessionThread::run()
+{
+ m_socket = new SessionSocket;
+ m_stream = new ImapStreamParser( m_socket );
+ connect( m_socket, SIGNAL(readyRead()),
+ this, SLOT(readMessage()), Qt::QueuedConnection );
+
+ connect( m_socket, SIGNAL(disconnected()),
+ m_session, SLOT(socketDisconnected()) );
+ connect( m_socket, SIGNAL(connected()),
+ m_session, SLOT(socketConnected()) );
+ connect( m_socket, SIGNAL(error(KTcpSocket::Error)),
+ m_session, SLOT(socketError()) );
+ connect( m_socket, SIGNAL(bytesWritten(qint64)),
+ m_session, SLOT(socketActivity()) );
+ if ( m_socket->metaObject()->indexOfSignal("encryptedBytesWritten(qint64)" ) > -1 ) {
+ connect( m_socket, SIGNAL(encryptedBytesWritten(qint64)), // needs kdelibs > 4.8
+ m_session, SLOT(socketActivity()) );
+ }
+ connect( m_socket, SIGNAL(readyRead()),
+ m_session, SLOT(socketActivity()) );
+
+ connect( this, SIGNAL(responseReceived(KIMAP::Message)),
+ m_session, SLOT(responseReceived(KIMAP::Message)) );
+
+ QTimer::singleShot( 0, this, SLOT(reconnect()) );
+ exec();
+
+ delete m_stream;
+ delete m_socket;
+}
+
+void SessionThread::startSsl(const KTcpSocket::SslVersion &version)
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_socket->setAdvertisedSslVersion(version);
+ m_socket->ignoreSslErrors();
+ connect(m_socket, SIGNAL(encrypted()), this, SLOT(sslConnected()));
+ m_socket->startClientEncryption();
+}
+
+void SessionThread::sslConnected()
+{
+ QMutexLocker locker(&m_mutex);
+ KSslCipher cipher = m_socket->sessionCipher();
+
+ if ( m_socket->sslErrors().count() > 0 || m_socket->encryptionMode() != KTcpSocket::SslClientMode
+ || cipher.isNull() || cipher.usedBits() == 0) {
+ kDebug() << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull()
+ << ", cipher.usedBits() is" << cipher.usedBits()
+ << ", the socket says:" << m_socket->errorString()
+ << "and the list of SSL errors contains"
+ << m_socket->sslErrors().count() << "items.";
+ KSslErrorUiData errorData(m_socket);
+ emit sslError(errorData);
+ } else {
+ kDebug() << "TLS negotiation done.";
+ m_encryptedMode = true;
+ emit encryptionNegotiationResult(true, m_socket->negotiatedSslVersion());
+ }
+}
+
+void SessionThread::sslErrorHandlerResponse(bool response)
+{
+ QMutexLocker locker(&m_mutex);
+ if (response) {
+ m_encryptedMode = true;
+ emit encryptionNegotiationResult(true, m_socket->negotiatedSslVersion());
+ } else {
+ m_encryptedMode = false;
+ //reconnect in unencrypted mode, so new commands can be issued
+ m_socket->disconnectFromHost();
+ m_socket->waitForDisconnected();
+ m_socket->connectToHost(m_hostName, m_port);
+ emit encryptionNegotiationResult(false, KTcpSocket::UnknownSslVersion);
+ }
+}
+
+#include "moc_sessionthread_p.cpp"
+
diff --git a/kimap/sessionthread_p.h b/kimap/sessionthread_p.h
new file mode 100644
index 0000000..672c418
--- /dev/null
+++ b/kimap/sessionthread_p.h
@@ -0,0 +1,85 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SESSIONTHREAD_P_H
+#define KIMAP_SESSIONTHREAD_P_H
+
+#include <QtCore/QMutex>
+#include <QtCore/QQueue>
+#include <QtCore/QThread>
+
+#include <ktcpsocket.h>
+
+typedef KTcpSocket SessionSocket;
+
+namespace KIMAP {
+
+class ImapStreamParser;
+struct Message;
+class Session;
+
+class SessionThread : public QThread
+{
+ Q_OBJECT
+
+ public:
+ explicit SessionThread( const QString &hostName, quint16 port, Session *parent );
+ ~SessionThread();
+
+ inline QString hostName() { return m_hostName; }
+ inline quint16 port() { return m_port; }
+
+ void sendData( const QByteArray &payload );
+ void run();
+
+ public slots:
+ void closeSocket();
+ void reconnect();
+ void startSsl(const KTcpSocket::SslVersion &version);
+
+ signals:
+ void responseReceived(const KIMAP::Message &response);
+ void encryptionNegotiationResult(bool, KTcpSocket::SslVersion);
+ void sslError(const KSslErrorUiData&);
+
+ private slots:
+ void readMessage();
+ void writeDataQueue();
+ void sslConnected();
+ void sslErrorHandlerResponse(bool result);
+ void doCloseSocket();
+
+ private:
+ QString m_hostName;
+ quint16 m_port;
+
+ Session *m_session;
+ SessionSocket *m_socket;
+ ImapStreamParser *m_stream;
+
+ QQueue<QByteArray> m_dataQueue;
+
+ QMutex m_mutex;
+
+ bool m_encryptedMode;
+};
+
+}
+
+#endif
diff --git a/kimap/sessionuiproxy.h b/kimap/sessionuiproxy.h
new file mode 100644
index 0000000..4e5bc66
--- /dev/null
+++ b/kimap/sessionuiproxy.h
@@ -0,0 +1,68 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SESSIONUIPROXY_H
+#define KIMAP_SESSIONUIPROXY_H
+
+#include <boost/shared_ptr.hpp>
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+class KSslErrorUiData;
+
+namespace KIMAP {
+
+/** @short Interface to display communication errors and wait for user feedback. */
+class KIMAP_EXPORT SessionUiProxy
+{
+ public:
+ typedef boost::shared_ptr<SessionUiProxy> Ptr;
+
+ virtual ~SessionUiProxy() {};
+ /**
+ * Show an SSL error and ask the user whether it should be ignored or not.
+ * The recommended KDE UI is the following:
+ * @code
+ * #include <kio/ksslui.h>
+ * class UiProxy: public SessionUiProxy {
+ * public:
+ * bool ignoreSslError(const KSslErrorUiData& errorData) {
+ * if (KIO::SslUi::askIgnoreSslErrors(errorData)) {
+ * return true;
+ * } else {
+ * return false;
+ * }
+ * }
+ * };
+ * [...]
+ * Session session(server, port);
+ * UiProxy *proxy = new UiProxy();
+ * session.setUiProxy(proxy);
+ * @endcode
+ * @param errorData contains details about the error.
+ * @return true if the error can be ignored
+ */
+ virtual bool ignoreSslError(const KSslErrorUiData& errorData) = 0;
+};
+
+}
+
+#endif
diff --git a/kimap/setacljob.cpp b/kimap/setacljob.cpp
new file mode 100644
index 0000000..ebc5d65
--- /dev/null
+++ b/kimap/setacljob.cpp
@@ -0,0 +1,81 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "setacljob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "acljobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class SetAclJobPrivate : public AclJobBasePrivate
+ {
+ public:
+ SetAclJobPrivate( Session *session, const QString& name ) : AclJobBasePrivate(session, name) {}
+ ~SetAclJobPrivate() { }
+ };
+}
+
+using namespace KIMAP;
+
+SetAclJob::SetAclJob( Session *session )
+ : AclJobBase(*new SetAclJobPrivate(session, i18n("SetAcl")))
+{
+
+}
+
+SetAclJob::~SetAclJob()
+{
+}
+
+void SetAclJob::doStart()
+{
+ Q_D(SetAclJob);
+ QByteArray r = Acl::rightsToString( d->rightList );
+ if (d->modifier == Add) {
+ r.prepend('+');
+ } else if (d->modifier == Remove) {
+ r.prepend('-');
+ }
+ d->tags << d->sessionInternal()->sendCommand( "SETACL", '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + "\" \"" + d->id + "\" \"" + r +'\"');
+}
+
+void SetAclJob::setRights(AclModifier modifier, Acl::Rights rights)
+{
+ Q_D(SetAclJob);
+ d->setRights(modifier, rights);
+}
+
+void SetAclJob::setIdentifier( const QByteArray &identifier )
+{
+ Q_D(SetAclJob);
+ d->setIdentifier(identifier);
+}
+
+QByteArray SetAclJob::identifier()
+{
+ Q_D(SetAclJob);
+ return d->identifier();
+}
+
diff --git a/kimap/setacljob.h b/kimap/setacljob.h
new file mode 100644
index 0000000..33df18d
--- /dev/null
+++ b/kimap/setacljob.h
@@ -0,0 +1,113 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SETACLJOB_H
+#define KIMAP_SETACLJOB_H
+
+#include "kimap_export.h"
+
+#include "acljobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class SetAclJobPrivate;
+
+
+/**
+ * Sets the rights that correspond to an identifier on a mailbox
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * This job requires that the server supports the ACL
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc4314.html">RFC 4314</a>.
+ */
+class KIMAP_EXPORT SetAclJob : public AclJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(SetAclJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit SetAclJob( Session *session );
+ virtual ~SetAclJob();
+
+ /**
+ * Sets the rights that will be changed for the identifier
+ *
+ * Note that multiple calls to this method will have a
+ * non-intuitive effect: the @p modifier value of the most
+ * recent call will be used, but the OR'd-together values
+ * of all calls to setRights() will be used.
+ *
+ * If the server does not recognise any of the rights,
+ * the job will fail and the ACL for the mailbox will
+ * remain unchanged.
+ *
+ * Note that some rights may be tied together, and must be set
+ * or removed as a group. See ListRightsJob::possibleRights()
+ * for more details. The server will only set a tied group
+ * of rights if you have requested that all the rights in that
+ * group should be set.
+ *
+ * @param modifier determines whether the rights will be
+ * added to the identifier, removed from
+ * the identifier or will replace any
+ * existing rights assigned to the
+ * identifier
+ * @param rights the rights to be added, removed or set
+ */
+ void setRights(AclModifier modifier, Acl::Rights rights);
+
+ /**
+ * Sets the identifier the rights will be modified for
+ *
+ * The meaning of identifiers depends on the server implementation,
+ * with the following restrictions:
+ *
+ * - "anyone" means any authenticated user, including anonymous
+ * - an identifier starting with a minus sign ('-') indicates
+ * "negative rights": rights that should be taken away from
+ * matching users
+ *
+ * Other than the above restrictions, ACL identifiers are usually
+ * IMAP usernames, but could potentially be group names as well.
+ *
+ * Note that negative rights override positive rights: if
+ * "fred" and "-fred" are both assigned the 'w' right, the
+ * user "fred" will not have the 'w' right.
+ */
+ void setIdentifier( const QByteArray &identifier );
+ /**
+ * The identifier that rights will be associated with
+ */
+ QByteArray identifier();
+
+ protected:
+ virtual void doStart();
+
+};
+
+}
+
+#endif
diff --git a/kimap/setmetadatajob.cpp b/kimap/setmetadatajob.cpp
new file mode 100644
index 0000000..7fa0ebb
--- /dev/null
+++ b/kimap/setmetadatajob.cpp
@@ -0,0 +1,153 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "setmetadatajob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "metadatajobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class SetMetaDataJobPrivate : public MetaDataJobBasePrivate
+ {
+ public:
+ SetMetaDataJobPrivate( Session *session, const QString& name ) : MetaDataJobBasePrivate(session, name), metaDataErrors(0), maxAcceptedSize(-1) { }
+ ~SetMetaDataJobPrivate() { }
+
+ QMap<QByteArray, QByteArray> entries;
+ QMap<QByteArray, QByteArray>::ConstIterator entriesIt;
+ QByteArray entryName;
+ SetMetaDataJob::MetaDataErrors metaDataErrors;
+ qint64 maxAcceptedSize;
+ };
+}
+
+using namespace KIMAP;
+
+SetMetaDataJob::SetMetaDataJob( Session *session )
+ : MetaDataJobBase( *new SetMetaDataJobPrivate(session, i18n("SetMetaData")) )
+{
+}
+
+SetMetaDataJob::~SetMetaDataJob()
+{
+}
+
+void SetMetaDataJob::doStart()
+{
+ Q_D(SetMetaDataJob);
+ QByteArray parameters;
+ parameters = '\"' + KIMAP::encodeImapFolderName( d->mailBox.toUtf8() ) + "\" ";
+ d->entriesIt = d->entries.constBegin();
+
+ QByteArray command = "SETMETADATA";
+ if (d->serverCapability == Annotatemore) {
+ command = "SETANNOTATION";
+ parameters += '\"' + d->entryName + "\" (";
+ d->m_name = i18n("SetAnnotation");
+ if (!d->entries.isEmpty()) {
+ for (; d->entriesIt != d->entries.constEnd(); ++d->entriesIt) {
+ parameters += '\"' + d->entriesIt.key() + "\" \"" + d->entriesIt.value() + "\" ";
+ }
+ parameters[parameters.length() - 1] = ')';
+ }
+ } else {
+ parameters += '(';
+ if (!d->entries.isEmpty()) {
+ parameters += '\"' + d->entriesIt.key() + '\"';
+ parameters += ' ';
+ parameters +=" {" + QByteArray::number(d->entriesIt.value().size()) + '}';
+ }
+ }
+
+ if (d->entries.isEmpty()) {
+ parameters += ')';
+ }
+
+ d->tags << d->sessionInternal()->sendCommand( command, parameters );
+// kDebug() << "SENT: " << command << " " << parameters;
+}
+
+void SetMetaDataJob::handleResponse( const Message &response )
+{
+ Q_D(SetMetaDataJob);
+
+ //TODO: Test if a server can really return more then one untagged NO response. If not, no need to OR the error codes
+ if ( !response.content.isEmpty()
+ && d->tags.contains( response.content.first().toString() ) ) {
+ if ( response.content[1].toString() == "NO" ) {
+ setError( UserDefinedError );
+ setErrorText( i18n("%1 failed, server replied: %2", d->m_name, response.toString().constData()) );
+ if (response.content[2].toString() == "[ANNOTATEMORE TOOMANY]" || response.content[2].toString() == "[METADATA TOOMANY]") {
+ d->metaDataErrors |= TooMany;
+ } else if (response.content[2].toString() == "[ANNOTATEMORE TOOBIG]" || response.content[2].toString().startsWith("[METADATA MAXSIZE")) {
+ d->metaDataErrors |= TooBig;
+ d->maxAcceptedSize = -1;
+ if (response.content[2].toString().startsWith("[METADATA MAXSIZE")) { //krazy:exclude=strings
+ QByteArray max = response.content[2].toString();
+ max.replace("[METADATA MAXSIZE",""); //krazy:exclude=doublequote_chars
+ max.replace("]", ""); //krazy:exclude=doublequote_chars
+ d->maxAcceptedSize = max.toLongLong();
+ }
+ } else if (response.content[2].toString() == "[METADATA NOPRIVATE]") {
+ d->metaDataErrors |= NoPrivate;
+ }
+ } else if ( response.content.size() < 2 ) {
+ setErrorText( i18n("%1 failed, malformed reply from the server.", d->m_name) );
+ } else if ( response.content[1].toString() != "OK" ) {
+ setError( UserDefinedError );
+ setErrorText( i18n("%1 failed, server replied: %2", d->m_name, response.toString().constData()) );
+ }
+ emitResult();
+ } else if ( d->serverCapability == Metadata && response.content[0].toString() == "+" ) {
+ QByteArray content = d->entriesIt.value();
+ ++d->entriesIt;
+ if (d->entriesIt == d->entries.constEnd()) {
+ content += ')';
+ } else {
+ content +=" {" + QByteArray::number(d->entriesIt.value().size()) + '}';
+ }
+// kDebug() << "SENT: " << content;
+ d->sessionInternal()->sendData( content );
+ }
+}
+
+void SetMetaDataJob::addMetaData(const QByteArray &name, const QByteArray &value)
+{
+ Q_D(SetMetaDataJob);
+ d->entries[name] = value;
+}
+
+void SetMetaDataJob::setEntry(const QByteArray &entry)
+{
+ Q_D(SetMetaDataJob);
+ d->entryName = entry;
+}
+
+SetMetaDataJob::MetaDataErrors SetMetaDataJob::metaDataErrors() const
+{
+ Q_D(const SetMetaDataJob);
+ return d->metaDataErrors;
+}
+
diff --git a/kimap/setmetadatajob.h b/kimap/setmetadatajob.h
new file mode 100644
index 0000000..1391ecc
--- /dev/null
+++ b/kimap/setmetadatajob.h
@@ -0,0 +1,216 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SETMETADATAJOB_H
+#define KIMAP_SETMETADATAJOB_H
+
+#include "kimap_export.h"
+
+#include "metadatajobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class SetMetaDataJobPrivate;
+
+/**
+ * Sets mailbox metadata.
+ *
+ * Provides support for the IMAP METADATA extension; both the
+ * final RFC version
+ * (<a href="http://tools.ietf.org/html/rfc5464">RFC 5464</a>)
+ * and the older, incompatible draft version (known as ANNOTATEMORE)
+ * (<a
+ * href="http://tools.ietf.org/html/draft-daboo-imap-annotatemore-07"
+ * >draft-daboo-imap-annotatemore-07</a>). See setServerCompatibility().
+ *
+ * Note that in Annotatemore mode, this job can only operate on
+ * one metadata entry at once.
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * If the server supports ACLs, the user will need the
+ * Acl::Lookup right on the mailbox, as well as one of
+ * - Acl::Read
+ * - Acl::KeepSeen
+ * - Acl::Write
+ * - Acl::Insert
+ * - Acl::Post
+ * Otherwise, the user must be able to list the mailbox
+ * and either read or write the message content.
+ *
+ * Note that even if the user has these permissions, the
+ * server may refuse to allow the user to write metadata
+ * based on some other criteria.
+ *
+ * Note also that on servers that implement the Annotatemore
+ * version of the extension, only Acl::Lookup rights are
+ * required (ie: the user must be able to list the mailbox).
+ */
+class KIMAP_EXPORT SetMetaDataJob : public MetaDataJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(SetMetaDataJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit SetMetaDataJob( Session *session );
+ virtual ~SetMetaDataJob();
+
+ /**
+ * Adds a metadata entry or attribute to the list of modifications to make
+ *
+ * When in Metadata mode, this method adds a metadata
+ * entry to the list of metadata additions and updates that
+ * will be performed when the job is run.
+ *
+ * @p name must be a valid ASCII string and may not contain two
+ * consecutive forward slashes ('/'), must not end with '/' and
+ * must not contain '*', '%', non-ASCII characters or characters
+ * in the ASCII range 0x00 to 0x19 (in practice, all control
+ * characters should be avoided). The name is case-insensitive.
+ *
+ * The first part of the entry name should be "/private" or
+ * "/shared", indicating the scope of the entry. Note that
+ * private metadata may not be supported by all servers.
+ *
+ * Server metadata entry names include:
+ * - /shared/comment
+ * - /shared/admin - a URI for contacting the server administrator
+ * (eg: a mailto: or tel: URI)
+ * - /shared/vendor/<vendor-token>/something
+ * - /private/vendor/<vendor-token>/something
+ *
+ * Mailbox metadata entry names include:
+ * - /shared/comment
+ * - /private/comment
+ * - /shared/vendor/<vendor-token>/something
+ * - /private/vendor/<vendor-token>/something
+ *
+ * @p value can be any data, although if it is a multi-line string
+ * value, CRLF line-endings must be used.
+ *
+ * When in Annotatemore mode, this method adds an attribute
+ * entry to the list of additions and updates that will be
+ * performed on the metadata entry when the job is run.
+ *
+ * @p name must be a valid UTF-8 string, and may not contain the
+ * '%' or '*' characters, or NUL. Use of non-visible UTF-8 characters
+ * is strongly discouraged.
+ *
+ * Possible attribute name prefixes are:
+ * - value - the data value of the attribute
+ * - content-type - a MIME content type and subtype
+ * - content-language - a RFC 3282 language code
+ * - vendor.<vendor-token> - a vendor-specific attribute
+ *
+ * Attribute names an attribute name prefix followed by ".priv" for
+ * private attributes or ".shared" for shared attributes. Note that
+ * the attributes "size.priv" and "size.shared" are read-only
+ * attributes set by the server, and so cannot be used with
+ * SetMetaDataJob.
+ *
+ * @param name the metadata entry name (Metadata mode) in ASCII or
+ * attribute name (Annotatemore mode) in UTF-8
+ * @param value the value of the entry or attribute
+ */
+ void addMetaData(const QByteArray &name, const QByteArray &value);
+
+ /**
+ * Sets the metadata entry name to operate on (in Annotatemore mode)
+ *
+ * In Annotatemore mode, this specifies the metadata entry name to
+ * operate on. For server metadata, this is one of:
+ * - /comment
+ * - /motd
+ * - /admin
+ * - /vendor/<vendor-token>/something
+ *
+ * For mailbox metadata, this is one of:
+ * - /comment
+ * - /sort
+ * - /thread
+ * - /check
+ * - /checkperiod
+ * - /vendor/<vendor-token>/something
+ *
+ * Entry names must be valid UTF-8 strings that do not contain the
+ * '%' or '*' characters, or NUL. Use of non-visible UTF-8
+ * characters is strongly discouraged.
+ *
+ * In Metadata mode, this has no effect. Metadata entry names
+ * should instead be specified as the first argument to addMetaData().
+ *
+ * @see setServerCapability()
+ *
+ * @param entry the metadata entry name in UTF-8
+ */
+ // KDE5: this API is horrible for Annotatemore mode: use an overload of
+ // addMetaData() instead:
+ // void addMetaData(const QString &entry, const QByteArray &value,
+ // const QByteArray &encoding = QByteArray(),
+ // const QByteArray &language = QByteArray(),
+ // const QMap<QString,QByteArray> vendorAttributes =
+ // QMap<QString,QByteArray>());
+ void setEntry(const QByteArray &entry);
+
+ /**
+ * Possible error codes that may be returned by the server.
+ */
+ enum MetaDataError {
+ NoError = 0, /**< Used to indicate that no errors have been received */
+ TooMany = 1, /**< Cannot add a new metadata item, because the limit has already been reached */
+ TooBig = 2, /**< A metadata value was too big (see maxAcceptedSize()) */
+ NoPrivate = 4 /**< The server does not support private metadata entries */
+ };
+
+ // Q_DECLARE_WHATEVER_THAT_WAS missing
+ Q_DECLARE_FLAGS(MetaDataErrors, MetaDataError)
+
+ /**
+ * The metadata errors received from the server.
+ *
+ * @return a set of error codes
+ */
+ MetaDataErrors metaDataErrors() const;
+ /**
+ * The maximum accepted metadata size.
+ *
+ * If the server replied that one of the metadata values was too
+ * large (see metaDataErrors), this should indicate what the
+ * maximum size accepted by the server is.
+ *
+ * @return the maximum value size in octets, or -1 if the limit is unknown
+ */
+ qint64 maxAcceptedSize();
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse( const Message &response );
+
+};
+
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( KIMAP::SetMetaDataJob::MetaDataErrors )
+
+#endif
diff --git a/kimap/setquotajob.cpp b/kimap/setquotajob.cpp
new file mode 100644
index 0000000..1e3b3f1
--- /dev/null
+++ b/kimap/setquotajob.cpp
@@ -0,0 +1,104 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "setquotajob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "quotajobbase_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class SetQuotaJobPrivate : public QuotaJobBasePrivate
+ {
+ public:
+ SetQuotaJobPrivate( Session *session, const QString& name ) : QuotaJobBasePrivate(session, name) { }
+ ~SetQuotaJobPrivate() { }
+
+ QMap<QByteArray, qint64> setList;
+ QByteArray root;
+ };
+}
+
+using namespace KIMAP;
+
+SetQuotaJob::SetQuotaJob( Session *session )
+ : QuotaJobBase( *new SetQuotaJobPrivate(session, i18n("SetQuota")) )
+{
+}
+
+SetQuotaJob::~SetQuotaJob()
+{
+}
+
+void SetQuotaJob::doStart()
+{
+ Q_D(SetQuotaJob);
+ QByteArray s;
+ s += '(';
+ for (QMap<QByteArray, qint64>::ConstIterator it = d->setList.constBegin(); it != d->setList.constEnd(); ++it ) {
+ s += it.key() + ' ' + QByteArray::number(it.value()) + ' ';
+ }
+ if (d->setList.isEmpty()) {
+ s += ')';
+ } else {
+ s[s.length() - 1] = ')';
+ }
+
+ kDebug() << "SETQUOTA " << '\"' + d->root + "\" " + s;
+ //XXX: [alexmerry, 2010-07-24]: should d->root be quoted properly?
+ d->tags << d->sessionInternal()->sendCommand( "SETQUOTA", '\"' + d->root + "\" " + s);
+}
+
+void SetQuotaJob::handleResponse(const Message &response)
+{
+ Q_D(SetQuotaJob);
+ if (handleErrorReplies(response) == NotHandled) {
+ if ( response.content.size() >= 4
+ && response.content[1].toString() == "QUOTA" ) {
+ d->quota = d->readQuota(response.content[3]);
+ }
+ }
+}
+
+
+void SetQuotaJob::setQuota(const QByteArray &resource, qint64 limit)
+{
+ Q_D(SetQuotaJob);
+
+ d->setList[resource.toUpper()] = limit;
+}
+
+void SetQuotaJob::setRoot(const QByteArray& root)
+{
+ Q_D(SetQuotaJob);
+
+ d->root = root;
+}
+
+QByteArray SetQuotaJob::root() const
+{
+ Q_D(const SetQuotaJob);
+
+ return d->root;
+}
+
diff --git a/kimap/setquotajob.h b/kimap/setquotajob.h
new file mode 100644
index 0000000..f3fac37
--- /dev/null
+++ b/kimap/setquotajob.h
@@ -0,0 +1,103 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SETQUOTAJOB_H
+#define KIMAP_SETQUOTAJOB_H
+
+#include "quotajobbase.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class SetQuotaJobPrivate;
+
+/**
+ * Sets resource limits on a quota root.
+ *
+ * Quotas are defined with respect to "resources" and "quota roots".
+ * A resource is a numerical property that can be limited, such
+ * as the octet size of all the messages in a mailbox, or the
+ * number of messages in a mailbox. Each mailbox has one or more
+ * quota roots, which are where the resource limits are defined.
+ * A quota root may or may not be a mailbox name, and an empty
+ * string is a valid quota root. All mailboxes with the same quota
+ * root share the resource limits of the quota root.
+ *
+ * This job can only be run when the session is in the
+ * authenticated (or selected) state.
+ *
+ * This job requires that the server supports the QUOTA
+ * capability, defined in
+ * <a href="http://www.apps.ietf.org/rfc/rfc2087.html">RFC 2087</a>.
+ */
+class KIMAP_EXPORT SetQuotaJob : public QuotaJobBase
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE( SetQuotaJob )
+
+ friend class SessionPrivate;
+
+ public:
+ explicit SetQuotaJob( Session *session );
+ virtual ~SetQuotaJob();
+
+ /**
+ * Set a limit for a quota resource.
+ *
+ * For example, you might set the limit for "STORAGE" to
+ * 512 to limit the sum of the messages' RFC822.SIZE to
+ * 512*1024 octets (ie: 512 kb), or the limit for "MESSAGE"
+ * to 100 to limit the number of messages to 100.
+ *
+ * Note that although RFC 2087 allows a resource name to
+ * be any string, this API actually limits resource names
+ * to upper-case atoms. In practice, resource names will
+ * almost certainly be composed entirely of upper-case latin
+ * letters (A-Z).
+ *
+ * @param resource the resource name
+ * @param limit the maximum value the resource may take
+ */
+ void setQuota( const QByteArray &resource, qint64 limit );
+
+ /**
+ * Set the quota root the resource limits should be set for.
+ *
+ * Note: if the quota root does not already exist, the server
+ * may create it and change the quota roots for any number of
+ * existing mailboxes in an implementation-defined manner.
+ *
+ * @see GetQuotaRootJob
+ */
+ void setRoot( const QByteArray &root );
+ /**
+ * The quota root that will be modified.
+ */
+ QByteArray root() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse( const Message &response );
+
+};
+
+}
+
+#endif
diff --git a/kimap/storejob.cpp b/kimap/storejob.cpp
new file mode 100644
index 0000000..7be31c5
--- /dev/null
+++ b/kimap/storejob.cpp
@@ -0,0 +1,194 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "storejob.h"
+
+#include <KDE/KDebug>
+#include <KDE/KLocale>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+
+namespace KIMAP
+{
+ class StoreJobPrivate : public JobPrivate
+ {
+ public:
+ StoreJobPrivate( Session *session, const QString& name ) : JobPrivate( session, name ) { }
+ ~StoreJobPrivate() { }
+
+ ImapSet set;
+ bool uidBased;
+ StoreJob::StoreMode mode;
+ MessageFlags flags;
+
+ QMap<int, MessageFlags> resultingFlags;
+ };
+}
+
+using namespace KIMAP;
+
+StoreJob::StoreJob( Session *session )
+ : Job( *new StoreJobPrivate(session, i18n("Store")) )
+{
+ Q_D(StoreJob);
+ d->uidBased = false;
+ d->mode = SetFlags;
+}
+
+StoreJob::~StoreJob()
+{
+}
+
+void StoreJob::setSequenceSet( const ImapSet &set )
+{
+ Q_D(StoreJob);
+ d->set = set;
+}
+
+ImapSet StoreJob::sequenceSet() const
+{
+ Q_D(const StoreJob);
+ return d->set;
+}
+
+void StoreJob::setUidBased(bool uidBased)
+{
+ Q_D(StoreJob);
+ d->uidBased = uidBased;
+}
+
+bool StoreJob::isUidBased() const
+{
+ Q_D(const StoreJob);
+ return d->uidBased;
+}
+
+void StoreJob::setFlags( const MessageFlags &flags )
+{
+ Q_D(StoreJob);
+ d->flags = flags;
+}
+
+MessageFlags StoreJob::flags() const
+{
+ Q_D(const StoreJob);
+ return d->flags;
+}
+
+void StoreJob::setMode( StoreMode mode )
+{
+ Q_D(StoreJob);
+ d->mode = mode;
+}
+
+StoreJob::StoreMode StoreJob::mode() const
+{
+ Q_D(const StoreJob);
+ return d->mode;
+}
+
+QMap<int, MessageFlags> StoreJob::resultingFlags() const
+{
+ Q_D(const StoreJob);
+ return d->resultingFlags;
+}
+
+void StoreJob::doStart()
+{
+ Q_D(StoreJob);
+
+ QByteArray parameters = d->set.toImapSequenceSet()+' ';
+
+ switch ( d->mode ) {
+ case SetFlags:
+ parameters+= "FLAGS";
+ break;
+ case AppendFlags:
+ parameters+= "+FLAGS";
+ break;
+ case RemoveFlags:
+ parameters+= "-FLAGS";
+ break;
+ }
+
+ parameters+=" (";
+ foreach ( const QByteArray &flag, d->flags ) {
+ parameters+=flag+' ';
+ }
+ if (!d->flags.isEmpty()) parameters.chop(1);
+ parameters+=')';
+
+ qDebug("%s", parameters.constData());
+
+ QByteArray command = "STORE";
+ if ( d->uidBased ) {
+ command = "UID "+command;
+ }
+
+ d->tags << d->sessionInternal()->sendCommand( command, parameters );
+}
+
+void StoreJob::handleResponse( const Message &response )
+{
+ Q_D(StoreJob);
+
+ if (handleErrorReplies(response) == NotHandled ) {
+ if ( response.content.size() == 4
+ && response.content[2].toString()=="FETCH"
+ && response.content[3].type()==Message::Part::List ) {
+
+ int id = response.content[1].toString().toInt();
+ qint64 uid = 0;
+ bool uidFound = false;
+ QList<QByteArray> resultingFlags;
+
+ QList<QByteArray> content = response.content[3].toList();
+
+ for ( QList<QByteArray>::ConstIterator it = content.constBegin();
+ it!=content.constEnd(); ++it ) {
+ QByteArray str = *it;
+ ++it;
+
+ if ( str=="FLAGS" ) {
+ if ( (*it).startsWith('(') && (*it).endsWith(')') ) {
+ QByteArray str = *it;
+ str.chop(1);
+ str.remove(0, 1);
+ resultingFlags = str.split(' ');
+ } else {
+ resultingFlags << *it;
+ }
+ } else if ( str=="UID" ) {
+ uid = it->toLongLong(&uidFound);
+ }
+ }
+
+ if ( !d->uidBased ) {
+ d->resultingFlags[id] = resultingFlags;
+ } else if ( uidFound ) {
+ d->resultingFlags[uid] = resultingFlags;
+ } else {
+ kWarning() << "We asked for UID but the server didn't give it back, resultingFlags not stored.";
+ }
+ }
+ }
+}
+
diff --git a/kimap/storejob.h b/kimap/storejob.h
new file mode 100644
index 0000000..7c41d90
--- /dev/null
+++ b/kimap/storejob.h
@@ -0,0 +1,75 @@
+/*
+ Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_STOREJOB_H
+#define KIMAP_STOREJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+#include "imapset.h"
+
+namespace KIMAP {
+
+class Session;
+struct Message;
+class StoreJobPrivate;
+
+typedef QList<QByteArray> MessageFlags;
+
+class KIMAP_EXPORT StoreJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(StoreJob)
+
+ friend class SessionPrivate;
+
+ public:
+ enum StoreMode
+ {
+ SetFlags,
+ AppendFlags,
+ RemoveFlags
+ };
+
+ explicit StoreJob( Session *session );
+ virtual ~StoreJob();
+
+ void setSequenceSet( const ImapSet &set );
+ ImapSet sequenceSet() const;
+
+ void setUidBased( bool uidBased );
+ bool isUidBased() const;
+
+ void setFlags( const MessageFlags &flags );
+ MessageFlags flags() const;
+
+ void setMode( StoreMode mode );
+ StoreMode mode() const;
+
+ QMap<int, MessageFlags> resultingFlags() const;
+
+ protected:
+ virtual void doStart();
+ virtual void handleResponse(const Message &response);
+};
+
+}
+
+#endif
diff --git a/kimap/subscribejob.cpp b/kimap/subscribejob.cpp
new file mode 100644
index 0000000..b3f9676
--- /dev/null
+++ b/kimap/subscribejob.cpp
@@ -0,0 +1,71 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "subscribejob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class SubscribeJobPrivate : public JobPrivate
+ {
+ public:
+ SubscribeJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~SubscribeJobPrivate() { }
+
+ QString mailBox;
+ };
+}
+
+using namespace KIMAP;
+
+SubscribeJob::SubscribeJob( Session *session )
+ : Job( *new SubscribeJobPrivate(session, i18n("Subscribe")) )
+{
+}
+
+SubscribeJob::~SubscribeJob()
+{
+}
+
+void SubscribeJob::doStart()
+{
+ Q_D(SubscribeJob);
+ d->tags << d->sessionInternal()->sendCommand( "SUBSCRIBE", '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"' );
+}
+
+void SubscribeJob::setMailBox( const QString &mailBox )
+{
+ Q_D(SubscribeJob);
+ d->mailBox = mailBox;
+}
+
+QString SubscribeJob::mailBox() const
+{
+ Q_D(const SubscribeJob);
+ return d->mailBox;
+}
+
+
diff --git a/kimap/subscribejob.h b/kimap/subscribejob.h
new file mode 100644
index 0000000..f0db503
--- /dev/null
+++ b/kimap/subscribejob.h
@@ -0,0 +1,52 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_SUBSCRIBEJOB_H
+#define KIMAP_SUBSCRIBEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+class SubscribeJobPrivate;
+
+class KIMAP_EXPORT SubscribeJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(SubscribeJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit SubscribeJob( Session *session );
+ virtual ~SubscribeJob();
+
+ void setMailBox( const QString &mailBox );
+ QString mailBox() const;
+
+ protected:
+ virtual void doStart();
+};
+
+}
+
+#endif
diff --git a/kimap/tests/CMakeLists.txt b/kimap/tests/CMakeLists.txt
new file mode 100644
index 0000000..8aa657a
--- /dev/null
+++ b/kimap/tests/CMakeLists.txt
@@ -0,0 +1,52 @@
+add_subdirectory(kimaptest)
+
+include_directories( ${CMAKE_SOURCE_DIR}/kimap ${Boost_INCLUDE_DIR})
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+
+MACRO(KIMAP_UNIT_TESTS)
+ FOREACH(_testname ${ARGN})
+ kde4_add_unit_test(${_testname} TESTNAME kimap-${_testname} NOGUI ${_testname}.cpp)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
+ target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY}
+ kimap kimaptest kmime)
+ add_definitions(-DTEST_DATA="\\"${CMAKE_CURRENT_SOURCE_DIR}\\"")
+ ENDFOREACH(_testname)
+ENDMACRO(KIMAP_UNIT_TESTS)
+
+MACRO(KIMAP_EXECUTABLE_TESTS)
+ FOREACH(_testname ${ARGN})
+ kde4_add_executable(${_testname} NOGUI TEST ${_testname}.cpp)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
+ target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} kimap kmime)
+ ENDFOREACH(_testname)
+ENDMACRO(KIMAP_EXECUTABLE_TESTS)
+
+########### automated tests ###############
+
+KIMAP_UNIT_TESTS(
+ fakeservertest
+ testrfccodecs
+ testsession
+ loginjobtest
+ logoutjobtest
+ capabilitiesjobtest
+ selectjobtest
+ createjobtest
+ deletejobtest
+ fetchjobtest
+ renamejobtest
+ subscribejobtest
+ unsubscribejobtest
+ listjobtest
+ storejobtest
+ imapsettest
+ idlejobtest
+ quotarootjobtest
+)
+
+########### manual tests ###############
+
+KIMAP_EXECUTABLE_TESTS(
+ testimapidle
+ testimapserver
+)
diff --git a/kimap/tests/capabilitiesjobtest.cpp b/kimap/tests/capabilitiesjobtest.cpp
new file mode 100644
index 0000000..d4ad29d
--- /dev/null
+++ b/kimap/tests/capabilitiesjobtest.cpp
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/capabilitiesjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+
+class CapabilitiesJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testCapabilities_data() {
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+ QTest::addColumn<QStringList>( "capabilities" );
+ QList<QByteArray> scenario;
+ scenario << "S: * PREAUTH"
+ << "C: A000001 CAPABILITY"
+ << "S: * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI"
+ << "S: A000001 OK CAPABILITY completed";
+
+ QStringList capabilities;
+ capabilities << "IMAP4REV1" << "STARTTLS" << "AUTH=GSSAPI";
+ QTest::newRow( "good" ) << scenario << capabilities;
+
+
+
+ scenario.clear();
+ capabilities.clear();
+ scenario << "S: * PREAUTH"
+ << "C: A000001 CAPABILITY"
+ << "S: A000001 BAD command unknown or arguments invalid";
+ QTest::newRow( "bad" ) << scenario << capabilities;
+
+
+
+ scenario.clear();
+ capabilities.clear();
+ scenario << "S: * PREAUTH"
+ << "C: A000001 CAPABILITY"
+ << "S: * CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN"
+ << "S: * some response"
+ << "S: A000001 OK CAPABILITY completed";
+
+ capabilities << "IMAP4REV1" << "STARTTLS" << "AUTH=PLAIN";
+ QTest::newRow( "extra-untagged" ) << scenario << capabilities;;
+}
+
+void testCapabilities()
+{
+ QFETCH( QList<QByteArray>, scenario );
+ QFETCH( QStringList, capabilities );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::CapabilitiesJob *job = new KIMAP::CapabilitiesJob(&session);
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD response", Continue);
+ QVERIFY(result);
+ if (result) {
+ QCOMPARE(job->capabilities(), capabilities);
+ }
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( CapabilitiesJobTest )
+
+#include "capabilitiesjobtest.moc"
diff --git a/kimap/tests/createjobtest.cpp b/kimap/tests/createjobtest.cpp
new file mode 100644
index 0000000..09f50c7
--- /dev/null
+++ b/kimap/tests/createjobtest.cpp
@@ -0,0 +1,88 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/createjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class CreateJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testCreate_data() {
+ QTest::addColumn<QString>( "mailbox" );
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 CREATE \"INBOX\""
+ << "S: A000001 OK CREATE completed";
+ QTest::newRow( "good" ) << "INBOX" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 CREATE \"INBOX-FAIL-BAD\""
+ << "S: A000001 BAD command unknown or arguments invalid";
+ QTest::newRow( "bad" ) << "INBOX-FAIL-BAD" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 CREATE \"INBOX-FAIL-NO\""
+ << "S: A000001 NO create failure";
+ QTest::newRow( "no" ) << "INBOX-FAIL-NO" << scenario;
+}
+
+void testCreate()
+{
+ QFETCH( QString, mailbox );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::CreateJob *job = new KIMAP::CreateJob(&session);
+ job->setMailBox(mailbox);
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD response", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO response", Continue);
+ QVERIFY(result);
+ if (result) {
+ QCOMPARE(job->mailBox(), mailbox);
+ }
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( CreateJobTest )
+
+#include "createjobtest.moc"
diff --git a/kimap/tests/deletejobtest.cpp b/kimap/tests/deletejobtest.cpp
new file mode 100644
index 0000000..3c8ed46
--- /dev/null
+++ b/kimap/tests/deletejobtest.cpp
@@ -0,0 +1,95 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/deletejob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class DeleteJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testDelete_data() {
+ QTest::addColumn<QString>( "mailbox" );
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 DELETE \"foo\""
+ << "S: A000001 OK DELETE completed";
+ QTest::newRow( "good" ) << "foo" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 DELETE \"foo-BAD\""
+ << "S: A000001 BAD command unknown or arguments invalid";
+ QTest::newRow( "bad" ) << "foo-BAD" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 DELETE \"foo\""
+ << "S: A000001 Name \"foo\" has inferior hierarchical names";
+ QTest::newRow( "no" ) << "foo" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 DELETE \"foo/bar\""
+ << "S: A000001 OK DELETE completed";
+ QTest::newRow( "hierarchical" ) << "foo/bar" << scenario;
+}
+
+void testDelete()
+{
+ QFETCH( QString, mailbox );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::DeleteJob *job = new KIMAP::DeleteJob(&session);
+ job->setMailBox(mailbox);
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD response", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO response", Continue);
+ QVERIFY(result);
+ if (result) {
+ QCOMPARE(job->mailBox(), mailbox);
+ }
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( DeleteJobTest )
+
+#include "deletejobtest.moc"
diff --git a/kimap/tests/fakeserverscenario.log b/kimap/tests/fakeserverscenario.log
new file mode 100644
index 0000000..f7d3f97
--- /dev/null
+++ b/kimap/tests/fakeserverscenario.log
@@ -0,0 +1,6 @@
+C: A000001 LIST "" *
+S: * LIST ( \HasChildren ) / INBOX
+S: * LIST ( \HasNoChildren ) / INBOX/&AOQ- &APY- &APw- @ &IKw-
+S: * LIST ( \HasChildren ) / INBOX/lost+found
+S: * LIST ( \HasNoChildren ) / "INBOX/lost+found/Calendar Public-20080128"
+S: A000001 OK LIST completed
diff --git a/kimap/tests/fakeservertest.cpp b/kimap/tests/fakeservertest.cpp
new file mode 100644
index 0000000..1399615
--- /dev/null
+++ b/kimap/tests/fakeservertest.cpp
@@ -0,0 +1,68 @@
+/*
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/listjob.h"
+
+#include <QtTest>
+
+class FakeServerTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testLoadScenario() {
+ KIMAP::MailBoxDescriptor descriptor;
+ QList<KIMAP::MailBoxDescriptor> listresult;
+
+ descriptor.separator = '/';
+ descriptor.name = "INBOX";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = QString::fromUtf8( "INBOX/ä ö ü @ €" );
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/lost+found";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/lost+found/Calendar Public-20080128";
+ listresult << descriptor;
+
+ FakeServer fakeServer;
+ fakeServer.addScenarioFromFile( TEST_DATA "/fakeserverscenario.log" );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::ListJob *job = new KIMAP::ListJob(&session);
+ job->setIncludeUnsubscribed(true);
+ QVERIFY(job->exec());
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( FakeServerTest )
+
+#include "fakeservertest.moc"
diff --git a/kimap/tests/fetchjobtest.cpp b/kimap/tests/fetchjobtest.cpp
new file mode 100644
index 0000000..749234c
--- /dev/null
+++ b/kimap/tests/fetchjobtest.cpp
@@ -0,0 +1,296 @@
+/*
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/fetchjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+Q_DECLARE_METATYPE(KIMAP::FetchJob::FetchScope)
+
+class FetchJobTest: public QObject {
+ Q_OBJECT
+
+private:
+ QStringList m_signals;
+
+ QMap<qint64, qint64> m_uids;
+ QMap<qint64, qint64> m_sizes;
+ QMap<qint64, KIMAP::MessageFlags> m_flags;
+ QMap<qint64, KIMAP::MessagePtr> m_messages;
+ QMap<qint64, KIMAP::MessageParts> m_parts;
+
+public slots:
+void onHeadersReceived( const QString &/*mailBox*/,
+ const QMap<qint64, qint64> &uids,
+ const QMap<qint64, qint64> &sizes,
+ const QMap<qint64, KIMAP::MessageFlags> &flags,
+ const QMap<qint64, KIMAP::MessagePtr> &messages )
+ {
+ m_signals << "headersReceived";
+ m_uids.unite( uids );
+ m_sizes.unite( sizes );
+ m_flags.unite( flags );
+ m_messages.unite( messages );
+ }
+
+void onMessagesReceived( QString, const QMap<qint64, qint64> uids, const QMap<qint64, KIMAP::MessagePtr> messages )
+{
+ m_signals << "messagesReceived";
+ m_uids.unite( uids );
+ m_messages.unite( messages );
+}
+
+void onPartsReceived( QString, QMap<qint64,qint64> /*uids*/, QMap<qint64, KIMAP::MessageParts> parts)
+{
+ m_signals << "partsReceived";
+ m_parts.unite( parts );
+}
+
+private Q_SLOTS:
+
+void testFetch_data() {
+ qRegisterMetaType<KIMAP::FetchJob::FetchScope>();
+
+ QTest::addColumn<bool>( "uidBased" );
+ QTest::addColumn< KIMAP::ImapSet >( "set" );
+ QTest::addColumn<int>( "expectedMessageCount" );
+ QTest::addColumn< QList<QByteArray> >( "scenario" );
+ QTest::addColumn<KIMAP::FetchJob::FetchScope>( "scope" );
+
+ KIMAP::FetchJob::FetchScope scope;
+ scope.mode = KIMAP::FetchJob::FetchScope::Flags;
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 FETCH 1:4 (FLAGS UID)"
+ << "S: * 1 FETCH ( FLAGS () UID 1 )"
+ << "S: * 2 FETCH ( FLAGS () UID 2 )"
+ << "S: * 3 FETCH ( FLAGS () UID 3 )"
+ << "S: * 4 FETCH ( FLAGS () UID 4 )"
+ << "S: A000001 OK fetch done";
+
+ QTest::newRow( "messages have empty flags" ) << false << KIMAP::ImapSet( 1, 4 ) << 4
+ << scenario << scope;
+
+ scenario.clear();
+ // kill the connection part-way through a list, with carriage returns at end
+ // BUG 253619
+ // this should fail, but it shouldn't crash
+ scenario << FakeServer::preauth()
+ << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)"
+ << "S: * 11 FETCH (RFC822.SIZE 770 INTERNALDATE \"11-Oct-2010 03:33:50 +0100\" BODY[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] {246}"
+ << "S: From: John Smith <jonathanr.smith@foobarbaz.com>\r\nTo: \"amagicemailaddress@foobarbazbarfoo.com\"\r\n\t<amagicemailaddress@foobarbazbarfoo.com>\r\nDate: Mon, 11 Oct 2010 03:34:48 +0100\r\nSubject: unsubscribe\r\nMessage-ID: <ASDFFDSASDFFDS@foobarbaz.com>\r\n\r\n"
+ << "X";
+ scope.mode = KIMAP::FetchJob::FetchScope::Headers;
+ QTest::newRow( "connection drop" ) << false << KIMAP::ImapSet( 11, 11 ) << 1 << scenario << scope;
+
+
+ scenario.clear();
+ // Important bit here if "([127.0.0.1])" which used to crash the stream parser
+ scenario << FakeServer::preauth()
+ << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)"
+ << "S: * 11 FETCH (RFC822.SIZE 770 INTERNALDATE \"11-Oct-2010 03:33:50 +0100\" BODY[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] {246}"
+ << "S: ([127.0.0.1])\r\nDate: Mon, 11 Oct 2010 03:34:48 +0100\r\nSubject: unsubscribe\r\nMessage-ID: <ASDFFDSASDFFDS@foobarbaz.com>\r\n\r\n"
+ << "X";
+ scope.mode = KIMAP::FetchJob::FetchScope::Headers;
+ QTest::newRow( "buffer overwrite" ) << false << KIMAP::ImapSet( 11, 11 ) << 1 << scenario << scope;
+
+
+ scenario.clear();
+ // We're assuming a buffer overwrite here which made us miss the opening parenthesis
+ // for the properties list
+ scenario << FakeServer::preauth()
+ << "C: A000001 FETCH 11 (RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] FLAGS UID)"
+ << "S: * 11 FETCH {10}doh!\r\n\r\n\r\n)\r\n"
+ << "X";
+ scope.mode = KIMAP::FetchJob::FetchScope::Headers;
+ QTest::newRow( "buffer overwrite 2" ) << false << KIMAP::ImapSet( 11, 11 ) << 1 << scenario << scope;
+}
+
+void testFetch()
+{
+ QFETCH( bool, uidBased );
+ QFETCH( KIMAP::ImapSet, set );
+ QFETCH( int, expectedMessageCount );
+ QFETCH( QList<QByteArray>, scenario );
+ QFETCH( KIMAP::FetchJob::FetchScope, scope );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::FetchJob *job = new KIMAP::FetchJob(&session);
+ job->setUidBased( uidBased );
+ job->setSequenceSet( set );
+ job->setScope( scope );
+
+ connect( job, SIGNAL(headersReceived(QString,
+ QMap<qint64, qint64>,
+ QMap<qint64, qint64>,
+ QMap<qint64, KIMAP::MessageFlags>,
+ QMap<qint64, KIMAP::MessagePtr>)),
+ this, SLOT(onHeadersReceived(QString,
+ QMap<qint64, qint64>,
+ QMap<qint64, qint64>,
+ QMap<qint64, KIMAP::MessageFlags>,
+ QMap<qint64, KIMAP::MessagePtr>)) );
+
+
+ bool result = job->exec();
+ QEXPECT_FAIL("connection drop", "Expected failure on connection drop", Continue);
+ QEXPECT_FAIL("buffer overwrite", "Expected failure on confused list", Continue);
+ QEXPECT_FAIL("buffer overwrite 2", "Expected beginning of message missing", Continue);
+ QVERIFY( result );
+ if ( result ) {
+ QVERIFY( m_signals.count()>0 );
+ QCOMPARE( m_uids.count(), expectedMessageCount );
+ }
+
+ QVERIFY( fakeServer.isAllScenarioDone() );
+ fakeServer.quit();
+
+ m_signals.clear();
+ m_uids.clear();
+ m_sizes.clear();
+ m_flags.clear();
+ m_messages.clear();
+ m_parts.clear();
+}
+
+void testFetchStructure()
+{
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 FETCH 1:2 (BODYSTRUCTURE UID)"
+ << "S: * 1 FETCH (UID 10 BODYSTRUCTURE (\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" 5 1 NIL NIL NIL))"
+ << "S: * 2 FETCH (UID 20 BODYSTRUCTURE ((((\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" 72 4 NIL NIL NIL)(\"TEXT\" \"HTML\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"QUOTED-PRINTABLE\" 281 5 NIL NIL NIL) \"ALTERNATIVE\" (\"BOUNDARY\" \"0001\") NIL NIL)(\"IMAGE\" \"GIF\" (\"NAME\" \"B56.gif\") \"<B56@goomoji.gmail>\" NIL \"BASE64\" 528 NIL NIL NIL) \"RELATED\" (\"BOUNDARY\" \"0002\") NIL NIL)(\"IMAGE\" \"JPEG\" (\"NAME\" \"photo.jpg\") NIL NIL \"BASE64\" 53338 NIL (\"ATTACHMENT\" (\"FILENAME\" \"photo.jpg\")) NIL) \"MIXED\" (\"BOUNDARY\" \"0003\") NIL NIL))"
+ << "S: A000001 OK fetch done";
+
+ KIMAP::FetchJob::FetchScope scope;
+ scope.mode = KIMAP::FetchJob::FetchScope::Structure;
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::FetchJob *job = new KIMAP::FetchJob(&session);
+ job->setUidBased( false );
+ job->setSequenceSet( KIMAP::ImapSet( 1, 2 ) );
+ job->setScope( scope );
+
+ connect( job, SIGNAL(messagesReceived(QString,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessagePtr>)),
+ SLOT(onMessagesReceived(QString,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessagePtr>)) );
+
+ bool result = job->exec();
+ QVERIFY( result );
+ QVERIFY( m_signals.count() > 0 );
+ QCOMPARE( m_uids.count(), 2 );
+ QCOMPARE( m_messages[1]->attachments().count(), 0 );
+ QCOMPARE( m_messages[2]->attachments().count(), 3 );
+ QCOMPARE( m_messages[2]->attachments().at(2)->contentDisposition()->filename(), QString("photo.jpg") );
+
+ fakeServer.quit();
+
+ m_signals.clear();
+ m_uids.clear();
+ m_sizes.clear();
+ m_flags.clear();
+ m_messages.clear();
+ m_parts.clear();
+}
+
+void testFetchParts()
+{
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 FETCH 2 (BODY.PEEK[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] BODY.PEEK[1.1.1.MIME] BODY.PEEK[1.1.1] FLAGS UID)"
+ << "S: * 2 FETCH (UID 20 FLAGS (\\Seen) BODY[HEADER.FIELDS (TO FROM MESSAGE-ID REFERENCES IN-REPLY-TO SUBJECT DATE)] {154}\r\nFrom: Joe Smith <smith@example.com>\r\nDate: Wed, 2 Mar 2011 11:33:24 +0700\r\nMessage-ID: <1234@example.com>\r\nSubject: hello\r\nTo: Jane <jane@example.com>\r\n\r\n BODY[1.1.1] {28}\r\nHi Jane, nice to meet you!\r\n BODY[1.1.1.MIME] {48}\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\n)\r\n"
+ << "S: A000001 OK fetch done";
+
+ KIMAP::FetchJob::FetchScope scope;
+ scope.mode = KIMAP::FetchJob::FetchScope::HeaderAndContent;
+ scope.parts.clear();
+ scope.parts.append("1.1.1");
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::FetchJob *job = new KIMAP::FetchJob(&session);
+ job->setUidBased( false );
+ job->setSequenceSet( KIMAP::ImapSet( 2, 2 ) );
+ job->setScope( scope );
+
+ connect ( job, SIGNAL(headersReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)),
+ SLOT(onHeadersReceived(QString,QMap<qint64,qint64>,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageFlags>,QMap<qint64,KIMAP::MessagePtr>)) );
+ connect( job, SIGNAL(partsReceived(QString,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageParts>)),
+ SLOT(onPartsReceived(QString,QMap<qint64,qint64>,QMap<qint64,KIMAP::MessageParts>)));
+
+ bool result = job->exec();
+
+ QVERIFY( result );
+ QVERIFY( m_signals.count() > 0 );
+ QCOMPARE( m_uids.count(), 1 );
+ QCOMPARE (m_parts.count(), 1 );
+
+ // Check that we received the message header
+ QCOMPARE( m_messages[2]->messageID()->identifier(), QByteArray("1234@example.com") );
+
+ // Check that we recieved the flags
+ QMap<qint64, KIMAP::MessageFlags> expectedFlags;
+ expectedFlags.insert(2, KIMAP::MessageFlags() << "\\Seen");
+ QCOMPARE( m_flags, expectedFlags );
+
+ // Check that we didn't received the full message body, since we only requested a specific part
+ QCOMPARE( m_messages[2]->decodedText().length(), 0 );
+ QCOMPARE( m_messages[2]->attachments().count(), 0);
+
+ // Check that we received the part we requested
+ QByteArray partId = m_parts[2].keys().first();
+ QString text = m_parts[2].value(partId)->decodedText(true, true);
+ QCOMPARE( partId, QByteArray("1.1.1"));
+ QCOMPARE( text, QString("Hi Jane, nice to meet you!")) ;
+
+ fakeServer.quit();
+
+ m_signals.clear();
+ m_uids.clear();
+ m_sizes.clear();
+ m_flags.clear();
+ m_messages.clear();
+ m_parts.clear();
+}
+
+};
+
+QTEST_KDEMAIN_CORE( FetchJobTest )
+
+#include "fetchjobtest.moc"
diff --git a/kimap/tests/idlejobtest.cpp b/kimap/tests/idlejobtest.cpp
new file mode 100644
index 0000000..0c35f49
--- /dev/null
+++ b/kimap/tests/idlejobtest.cpp
@@ -0,0 +1,216 @@
+/*
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/selectjob.h"
+#include "kimap/idlejob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+Q_DECLARE_METATYPE(QList<int>)
+Q_DECLARE_METATYPE(KIMAP::IdleJob*)
+
+class IdleJobTest: public QObject {
+ Q_OBJECT
+
+public:
+explicit IdleJobTest( QObject *parent = 0 )
+ : QObject( parent )
+{
+ qRegisterMetaType<KIMAP::IdleJob*>();
+}
+
+private Q_SLOTS:
+void shouldReactToIdle_data()
+{
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+ QTest::addColumn<QString>( "expectedMailBox" );
+ QTest::addColumn< QList<int> >( "expectedMessageCounts" );
+ QTest::addColumn< QList<int> >( "expectedRecentCounts" );
+
+ QList<QByteArray> scenario;
+ QString expectedMailBox;
+ QList<int> expectedMessageCounts;
+ QList<int> expectedRecentCounts;
+
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX/Foo\""
+ << "S: A000001 OK SELECT done"
+ << "C: A000002 IDLE"
+ << "S: + OK"
+ << "S: * 0 RECENT"
+ << "S: * 1 EXISTS"
+ << "S: * 1 RECENT"
+ << "S: * 2 EXISTS"
+ << "S: A000002 OK done idling";
+
+ expectedMailBox = "INBOX/Foo";
+
+ expectedMessageCounts.clear();
+ expectedRecentCounts.clear();
+
+ expectedMessageCounts << 1 << 2;
+ expectedRecentCounts << 0 << 1;
+
+ QTest::newRow( "normal" ) << scenario << expectedMailBox << expectedMessageCounts << expectedRecentCounts;
+
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX/Foo\""
+ << "S: A000001 OK SELECT done"
+ << "C: A000002 IDLE"
+ << "S: + OK"
+ << "S: * 0 RECENT"
+ << "S: * 1 RECENT"
+ << "S: A000002 OK done idling";
+
+ expectedMailBox = "INBOX/Foo";
+
+ expectedMessageCounts.clear();
+ expectedRecentCounts.clear();
+
+ expectedMessageCounts << -1 << -1;
+ expectedRecentCounts << 0 << 1;
+
+ QTest::newRow( "only RECENT" ) << scenario << expectedMailBox << expectedMessageCounts << expectedRecentCounts;
+
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX/Foo\""
+ << "S: A000001 OK SELECT done"
+ << "C: A000002 IDLE"
+ << "S: + OK"
+ << "S: * 1 EXISTS"
+ << "S: * 2 EXISTS"
+ << "S: A000002 OK done idling";
+
+ expectedMailBox = "INBOX/Foo";
+
+ expectedMessageCounts.clear();
+ expectedRecentCounts.clear();
+
+ expectedMessageCounts << 1 << 2;
+ expectedRecentCounts << -1 << -1;
+
+ QTest::newRow( "only EXISTS" ) << scenario << expectedMailBox << expectedMessageCounts << expectedRecentCounts;
+
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX/Foo\""
+ << "S: A000001 OK SELECT done"
+ << "C: A000002 IDLE"
+ << "S: + OK"
+ << "S: * 2 EXISTS"
+ << "W: 150"
+ << "S: * 1 RECENT"
+ << "S: A000002 OK done idling";
+
+ expectedMailBox = "INBOX/Foo";
+
+ expectedMessageCounts.clear();
+ expectedRecentCounts.clear();
+
+ expectedMessageCounts << 2;
+ expectedRecentCounts << 1;
+
+ QTest::newRow( "under 200ms, same notification" ) << scenario << expectedMailBox << expectedMessageCounts << expectedRecentCounts;
+
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX/Foo\""
+ << "S: A000001 OK SELECT done"
+ << "C: A000002 IDLE"
+ << "S: + OK"
+ << "S: * 2 EXISTS"
+ << "W: 250"
+ << "S: * 1 RECENT"
+ << "S: A000002 OK done idling";
+
+ expectedMailBox = "INBOX/Foo";
+
+ expectedMessageCounts.clear();
+ expectedRecentCounts.clear();
+
+ expectedMessageCounts << 2 << -1;
+ expectedRecentCounts << -1 << 1;
+
+ QTest::newRow( "above 200ms, two notifications" ) << scenario << expectedMailBox << expectedMessageCounts << expectedRecentCounts;
+}
+
+void shouldReactToIdle()
+{
+ QFETCH( QList<QByteArray>, scenario );
+ QFETCH( QString, expectedMailBox );
+ QFETCH( QList<int>, expectedMessageCounts );
+ QFETCH( QList<int>, expectedRecentCounts );
+
+ QVERIFY( expectedMessageCounts.size() == expectedRecentCounts.size() );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session( "127.0.0.1", 5989 );
+
+ KIMAP::SelectJob *select = new KIMAP::SelectJob( &session );
+ select->setMailBox( expectedMailBox );
+ QVERIFY( select->exec() );
+
+ KIMAP::IdleJob *idle = new KIMAP::IdleJob( &session );
+
+ QSignalSpy spy( idle, SIGNAL(mailBoxStats(KIMAP::IdleJob*,QString,int,int)) );
+
+ bool result = idle->exec();
+
+ if ( result ) {
+ QCOMPARE( spy.count(), expectedMessageCounts.size() );
+
+ for ( int i=0; i<spy.count(); i++ ) {
+ const KIMAP::IdleJob *job = spy.at( i ).at( 0 ).value<KIMAP::IdleJob*>();
+ const QString mailBox = spy.at( i ).at( 1 ).toString();
+ const int messageCount = spy.at( i ).at( 2 ).toInt();
+ const int recentCount = spy.at( i ).at( 3 ).toInt();
+
+ QCOMPARE( job, idle );
+ QCOMPARE( mailBox, expectedMailBox );
+ QCOMPARE( messageCount, expectedMessageCounts.at( i ) );
+ QCOMPARE( recentCount, expectedRecentCounts.at( i ) );
+ }
+ }
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( IdleJobTest )
+
+#include "idlejobtest.moc"
diff --git a/kimap/tests/imapsettest.cpp b/kimap/tests/imapsettest.cpp
new file mode 100644
index 0000000..56795f5
--- /dev/null
+++ b/kimap/tests/imapsettest.cpp
@@ -0,0 +1,76 @@
+/*
+ Copyright (C) 2009 Kevin Ottens <ervin@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimap/imapset.h"
+
+#include <QtTest>
+#include <KDebug>
+
+using namespace KIMAP;
+
+class ImapSetTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void shouldConvertToAndFromByteArray_data()
+ {
+ ImapSet set;
+
+ QTest::addColumn<ImapSet>( "imapSet" );
+ QTest::addColumn<QByteArray>( "byteArray" );
+
+ QTest::newRow( "empty set" ) << ImapSet() << QByteArray();
+ QTest::newRow( "unique value" ) << ImapSet( 7 ) << QByteArray( "7" );
+ QTest::newRow( "single interval" ) << ImapSet( 7, 10 ) << QByteArray( "7:10" );
+ QTest::newRow( "single interval with no upper bound" ) << ImapSet( 1, 0 ) << QByteArray( "1:*" );
+
+ set = ImapSet( 7, 10 );
+ set.add( ImapInterval( 12, 14 ) );
+ QTest::newRow( "two intervals" ) << set << QByteArray( "7:10,12:14" );
+
+ set = ImapSet( 7, 10 );
+ set.add( ImapInterval( 12 ) );
+ QTest::newRow( "two intervals with an infinite one" ) << set << QByteArray( "7:10,12:*" );
+
+ set = ImapSet( 7, 10 );
+ set.add( 5 );
+ QTest::newRow( "one interval and a value" ) << set << QByteArray( "7:10,5" );
+
+ set = ImapSet( 7, 10 );
+ set.add( QList<ImapSet::Id>() << 5 << 3 );
+ QTest::newRow( "one interval and two values" ) << set << QByteArray( "7:10,3,5" );
+ }
+
+ void shouldConvertToAndFromByteArray()
+ {
+ QFETCH( ImapSet, imapSet );
+ QFETCH( QByteArray, byteArray );
+
+ QCOMPARE( QString::fromUtf8( imapSet.toImapSequenceSet() ),
+ QString::fromUtf8( byteArray ) );
+ //qDebug() << "Expects" << imapSet << "got" << ImapSet::fromImapSequenceSet( byteArray );
+ QCOMPARE( ImapSet::fromImapSequenceSet( byteArray ), imapSet );
+ }
+};
+
+QTEST_KDEMAIN_CORE( ImapSetTest )
+
+#include "imapsettest.moc"
diff --git a/kimap/tests/kimaptest/CMakeLists.txt b/kimap/tests/kimaptest/CMakeLists.txt
new file mode 100644
index 0000000..80723fa
--- /dev/null
+++ b/kimap/tests/kimaptest/CMakeLists.txt
@@ -0,0 +1,24 @@
+project(kimaptest)
+
+if(KDE4_BUILD_TESTS)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
+
+set(kimaptest_LIB_SRCS
+ fakeserver.cpp
+ mockjob.cpp
+)
+
+kde4_add_library(kimaptest STATIC ${kimaptest_LIB_SRCS})
+target_link_libraries(kimaptest ${KDE4_KDECORE_LIBS} ${QT_QTNETWORK_LIBRARY} ${QT_QTTEST_LIBRARY} kimap)
+install(TARGETS kimaptest ${INSTALL_TARGETS_DEFAULT_ARGS})
+
+########### install files ###############
+
+install(FILES
+ fakeserver.h
+ mockjob.h
+ DESTINATION ${INCLUDE_INSTALL_DIR}/kimaptest COMPONENT Devel)
+
+endif()
+
diff --git a/kimap/tests/kimaptest/fakeserver.cpp b/kimap/tests/kimaptest/fakeserver.cpp
new file mode 100644
index 0000000..816f80f
--- /dev/null
+++ b/kimap/tests/kimaptest/fakeserver.cpp
@@ -0,0 +1,223 @@
+/*
+ Copyright (C) 2008 Omat Holding B.V. <info@omat.nl>
+
+ Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+// Own
+#include "fakeserver.h"
+
+// Qt
+#include <QDebug>
+#include <QTcpServer>
+
+// KDE
+#include <KDebug>
+#include <qtest_kde.h>
+
+#include "kimap/imapstreamparser.h"
+
+QByteArray FakeServer::preauth()
+{
+ return "S: * PREAUTH localhost Test Library server ready";
+}
+
+QByteArray FakeServer::greeting()
+{
+ return "S: * OK localhost Test Library server ready";
+}
+
+FakeServer::FakeServer( QObject* parent ) : QThread( parent )
+{
+ moveToThread(this);
+}
+
+
+FakeServer::~FakeServer()
+{
+ quit();
+ wait();
+}
+
+void FakeServer::startAndWait()
+{
+ start();
+ // this will block until the event queue starts
+ QMetaObject::invokeMethod( this, "started", Qt::BlockingQueuedConnection );
+}
+
+void FakeServer::dataAvailable()
+{
+ QMutexLocker locker(&m_mutex);
+
+ QTcpSocket *socket = qobject_cast<QTcpSocket*>( sender() );
+ Q_ASSERT( socket!=0 );
+
+ int scenarioNumber = m_clientSockets.indexOf( socket );
+
+ QVERIFY( !m_scenarios[scenarioNumber].isEmpty() );
+
+ readClientPart( scenarioNumber );
+ writeServerPart( scenarioNumber );
+}
+
+void FakeServer::newConnection()
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_clientSockets << m_tcpServer->nextPendingConnection();
+ connect(m_clientSockets.last(), SIGNAL(readyRead()), this, SLOT(dataAvailable()));
+ m_clientParsers << new KIMAP::ImapStreamParser( m_clientSockets.last(), true );
+
+ QVERIFY( m_clientSockets.size() <= m_scenarios.size() );
+
+ writeServerPart( m_clientSockets.size() - 1 );
+}
+
+void FakeServer::run()
+{
+ m_tcpServer = new QTcpServer();
+ if ( !m_tcpServer->listen( QHostAddress( QHostAddress::LocalHost ), 5989 ) ) {
+ kFatal() << "Unable to start the server";
+ }
+
+ connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
+
+ exec();
+
+ qDeleteAll( m_clientParsers );
+ qDeleteAll( m_clientSockets );
+
+ delete m_tcpServer;
+}
+
+void FakeServer::started()
+{
+ // do nothing: this is a dummy slot used by startAndWait()
+}
+
+void FakeServer::setScenario( const QList<QByteArray> &scenario )
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_scenarios.clear();
+ m_scenarios << scenario;
+}
+
+void FakeServer::addScenario( const QList<QByteArray> &scenario )
+{
+ QMutexLocker locker(&m_mutex);
+
+ m_scenarios << scenario;
+}
+
+void FakeServer::addScenarioFromFile( const QString &fileName )
+{
+ QFile file( fileName );
+ file.open( QFile::ReadOnly );
+
+ QList<QByteArray> scenario;
+
+ // When loading from files we never have the authentication phase
+ // force jumping directly to authenticated state.
+ scenario << preauth();
+
+ while ( !file.atEnd() ) {
+ scenario << file.readLine().trimmed();
+ }
+
+ file.close();
+
+ addScenario( scenario );
+}
+
+bool FakeServer::isScenarioDone( int scenarioNumber ) const
+{
+ QMutexLocker locker(&m_mutex);
+
+ if ( scenarioNumber < m_scenarios.size() ) {
+ return m_scenarios[scenarioNumber].isEmpty();
+ } else {
+ return true; // Non existent hence empty, right?
+ }
+}
+
+bool FakeServer::isAllScenarioDone() const
+{
+ QMutexLocker locker( &m_mutex );
+
+ foreach ( const QList<QByteArray> &scenario, m_scenarios ) {
+ if ( !scenario.isEmpty() ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void FakeServer::writeServerPart( int scenarioNumber )
+{
+ QList<QByteArray> scenario = m_scenarios[scenarioNumber];
+ QTcpSocket *clientSocket = m_clientSockets[scenarioNumber];
+
+ while ( !scenario.isEmpty()
+ && ( scenario.first().startsWith( "S: " ) || scenario.first().startsWith( "W: " ) ) ) {
+ QByteArray rule = scenario.takeFirst();
+
+ if ( rule.startsWith( "S: " ) ) {
+ QByteArray payload = rule.mid( 3 );
+ clientSocket->write( payload + "\r\n" );
+ } else {
+ int timeout = rule.mid( 3 ).toInt();
+ QTest::qWait( timeout );
+ }
+ }
+
+ if ( !scenario.isEmpty()
+ && scenario.first().startsWith( "X" ) ) {
+ scenario.takeFirst();
+ clientSocket->close();
+ }
+
+ if ( !scenario.isEmpty() ) {
+ QVERIFY( scenario.first().startsWith( "C: " ) );
+ }
+
+ m_scenarios[scenarioNumber] = scenario;
+}
+
+void FakeServer::readClientPart( int scenarioNumber )
+{
+ QList<QByteArray> scenario = m_scenarios[scenarioNumber];
+ KIMAP::ImapStreamParser *clientParser = m_clientParsers[scenarioNumber];
+
+ while ( !scenario.isEmpty()
+ && scenario.first().startsWith( "C: " ) ) {
+ QByteArray received = "C: "+clientParser->readUntilCommandEnd().trimmed();
+ QByteArray expected = scenario.takeFirst();
+ QCOMPARE( QString::fromUtf8( received ), QString::fromUtf8( expected ) );
+ QCOMPARE( received, expected );
+ }
+
+ if ( !scenario.isEmpty() ) {
+ QVERIFY( scenario.first().startsWith( "S: " ) );
+ }
+
+ m_scenarios[scenarioNumber] = scenario;
+}
+
diff --git a/kimap/tests/kimaptest/fakeserver.h b/kimap/tests/kimaptest/fakeserver.h
new file mode 100644
index 0000000..22b970c
--- /dev/null
+++ b/kimap/tests/kimaptest/fakeserver.h
@@ -0,0 +1,222 @@
+/*
+ Copyright (C) 2008 Omat Holding B.V. <info@omat.nl>
+
+ Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+
+#ifndef FAKESERVER_H
+#define FAKESERVER_H
+
+#include <QStringList>
+#include <QTcpSocket>
+#include <QTcpServer>
+#include <QThread>
+#include <QMutex>
+
+namespace KIMAP
+{
+ class ImapStreamParser;
+}
+
+Q_DECLARE_METATYPE( QList<QByteArray> )
+
+/**
+ * Pretends to be an IMAP server for the purposes of unit tests.
+ *
+ * FakeServer does not really understand the IMAP protocol. Instead,
+ * you give it a script, or scenario, that lists how an IMAP session
+ * exchange should go. When it receives the client parts of the
+ * scenario, it will respond with the following server parts.
+ *
+ * The server can be furnished with several scenarios. The first
+ * scenario will be played out to the first client that connects, the
+ * second scenario to the second client connection and so on.
+ *
+ * The fake server runs as a separate thread in the same process it
+ * is started from, and listens for connections on port 5989 on the
+ * local machine.
+ *
+ * Scenarios are in the form of protocol messages, with a tag at the
+ * start to indicate whether it is message that will be sent by the
+ * client ("C:") or a response that should be sent by the server
+ * ("S:"). For example:
+ * @code
+ * C: A000001 LIST "" *
+ * S: * LIST ( \HasChildren ) / INBOX
+ * S: * LIST ( \HasNoChildren ) / INBOX/&AOQ- &APY- &APw- @ &IKw-
+ * S: * LIST ( \HasChildren ) / INBOX/lost+found
+ * S: * LIST ( \HasNoChildren ) / "INBOX/lost+found/Calendar Public-20080128"
+ * S: A000001 OK LIST completed
+ * @endcode
+ *
+ * A line starting with X indicates that the connection should be
+ * closed by the server. This should be the last line in the
+ * scenario. For example, the following simulates the server closing
+ * the connection after receiving too many bad commands:
+ * @code
+ * C: A000001 madhatter
+ * S: A000001 BAD Command madhatter
+ * X
+ * @endcode
+ *
+ * FakeServer::preauth() and FakeServer::greeting() provide standard
+ * PREAUTH and OK responses, respectively, that can be used (unmodified)
+ * as the first line of a scenario.
+ *
+ * A typical usage is something like
+ * @code
+ * QList<QByteArray> scenario;
+ * scenario << FakeServer::preauth()
+ * << "C: A000001 CAPABILITY"
+ * << "S: * CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI"
+ * << "S: A000001 OK CAPABILITY completed";
+ *
+ * FakeServer fakeServer;
+ * fakeServer.setScenario( scenario );
+ * fakeServer.startAndWait();
+ *
+ * KIMAP::Session session( "127.0.0.1", 5989 );
+ * KIMAP::CapabilitiesJob *job = new KIMAP::CapabilitiesJob(&session);
+ * QVERIFY( job->exec() );
+ * // check the returned capabilities
+ *
+ * fakeServer.quit();
+ * @endcode
+ */
+class FakeServer : public QThread
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Get the default PREAUTH response
+ *
+ * This is the initial PREAUTH message that the server
+ * sends at the start of a session to indicate that the
+ * user is already authenticated by some other mechanism.
+ *
+ * Can be used as the first line in a scenario where
+ * you want to skip the LOGIN stage of the protocol.
+ */
+ static QByteArray preauth();
+ /**
+ * Get the default greeting
+ *
+ * This is the initial OK message that the server sends at the
+ * start of a session to indicate that a LOGIN is required.
+ *
+ * Can be used as the first line in a scenario where
+ * you want to use the LOGIN command.
+ */
+ static QByteArray greeting();
+
+ FakeServer( QObject* parent = 0 );
+ ~FakeServer();
+
+ /**
+ * Starts the server and waits for it to be ready
+ *
+ * You should use this instead of start() to avoid race conditions.
+ */
+ void startAndWait();
+
+ /**
+ * Starts the fake IMAP server
+ *
+ * You should not call this directly. Use start() instead.
+ *
+ * @reimp
+ */
+ virtual void run();
+
+ /**
+ * Removes any previously-added scenarios, and adds a new one
+ *
+ * After this, there will only be one scenario, and so the fake
+ * server will only be able to service a single request. More
+ * scenarios can be added with addScenario, though.
+ *
+ * @see addScenario()\n
+ * addScenarioFromFile()
+ */
+ void setScenario( const QList<QByteArray> &scenario );
+
+ /**
+ * Adds a new scenario
+ *
+ * Note that scenarios will be used in the order that clients
+ * connect. If this is the 5th scenario that has been added
+ * (bearing in mind that setScenario() resets the scenario
+ * count), it will be used to service the 5th client that
+ * connects.
+ *
+ * @see addScenarioFromFile()
+ *
+ * @param scenario the scenario as a list of messages
+ */
+ void addScenario( const QList<QByteArray> &scenario );
+ /**
+ * Adds a new scenario from a local file
+ *
+ * Note that scenarios will be used in the order that clients
+ * connect. If this is the 5th scenario that has been added
+ * (bearing in mind that setScenario() resets the scenario
+ * count), it will be used to service the 5th client that
+ * connects.
+ *
+ * @see addScenario()
+ *
+ * @param fileName the name of the file that contains the
+ * scenario; it will be split at line
+ * boundaries, and excess whitespace will
+ * be trimmed from the start and end of lines
+ */
+ void addScenarioFromFile( const QString &fileName );
+
+ /**
+ * Checks whether a particular scenario has completed
+ *
+ * @param scenarioNumber the number of the scenario to check,
+ * in order of addition/client connection
+ */
+ bool isScenarioDone( int scenarioNumber ) const;
+ /**
+ * Whether all the scenarios that were added to the fake
+ * server have been completed.
+ */
+ bool isAllScenarioDone() const;
+
+private Q_SLOTS:
+ void newConnection();
+ void dataAvailable();
+ void started();
+
+private:
+ void writeServerPart( int scenarioNumber );
+ void readClientPart( int scenarioNumber );
+
+ QList< QList<QByteArray> > m_scenarios;
+ QTcpServer *m_tcpServer;
+ mutable QMutex m_mutex;
+ QList<QTcpSocket*> m_clientSockets;
+ QList<KIMAP::ImapStreamParser*> m_clientParsers;
+};
+
+#endif
+
diff --git a/kimap/tests/kimaptest/mockjob.cpp b/kimap/tests/kimaptest/mockjob.cpp
new file mode 100644
index 0000000..e41257f
--- /dev/null
+++ b/kimap/tests/kimaptest/mockjob.cpp
@@ -0,0 +1,79 @@
+/*
+ This file is part of the KDE project
+ Copyright (C) 2008 Kevin Ottens <ervin@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+ */
+
+#include "mockjob.h"
+
+#include "../../job_p.h"
+#include "../../session.h"
+#include "../../session_p.h"
+
+#include <QtCore/QTimer>
+
+class MockJobPrivate : public KIMAP::JobPrivate
+{
+public:
+ MockJobPrivate( KIMAP::Session *session, const QString& name ) : KIMAP::JobPrivate(session, name) { }
+ ~MockJobPrivate() { }
+
+ QByteArray command;
+};
+
+MockJob::MockJob(KIMAP::Session *session)
+ : KIMAP::Job( *new MockJobPrivate(session, i18n("Mock")) )
+{
+}
+
+void MockJob::doStart()
+{
+ Q_D(MockJob);
+ if ( isNull() ) {
+ QTimer::singleShot(10, this, SLOT(done()));
+ } else {
+ d->sessionInternal()->setSocketTimeout( 10 );
+ d->tags << d->sessionInternal()->sendCommand( d->command );
+ }
+}
+
+void MockJob::done()
+{
+ emitResult();
+}
+
+void MockJob::setCommand(const QByteArray &command)
+{
+ Q_D(MockJob);
+ d->command = command;
+}
+
+QByteArray MockJob::command() const
+{
+ Q_D(const MockJob);
+ return d->command;
+}
+
+bool MockJob::isNull() const
+{
+ Q_D(const MockJob);
+ return d->command.isEmpty();
+}
+
diff --git a/kimap/tests/kimaptest/mockjob.h b/kimap/tests/kimaptest/mockjob.h
new file mode 100644
index 0000000..c5eb4de
--- /dev/null
+++ b/kimap/tests/kimaptest/mockjob.h
@@ -0,0 +1,75 @@
+/*
+ This file is part of the KDE project
+ Copyright (C) 2008 Kevin Ottens <ervin@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+ */
+
+#ifndef MOCKJOB_H
+#define MOCKJOB_H
+
+#include <kimap/job.h>
+
+class MockJobPrivate;
+
+/**
+ * Provides an easy way to send an arbitrary IMAP client command.
+ */
+class MockJob : public KIMAP::Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(MockJob)
+
+ public:
+ MockJob(KIMAP::Session *session);
+
+ /**
+ * Sets the command to execute.
+ *
+ * This should not include the command tag.
+ */
+ void setCommand(const QByteArray &command);
+ /**
+ * The command that will be sent.
+ */
+ QByteArray command() const;
+ /**
+ * Whether the command is empty.
+ *
+ * Equivalent to command().isEmpty().
+ *
+ * @return @c true if no command is set, @c false otherwise
+ */
+ bool isNull() const;
+
+ /**
+ * Starts the job.
+ *
+ * Do not call this directly. Use start() instead.
+ *
+ * @reimp
+ */
+ virtual void doStart();
+
+ private slots:
+ void done();
+};
+
+#endif
+
diff --git a/kimap/tests/listjobtest.cpp b/kimap/tests/listjobtest.cpp
new file mode 100644
index 0000000..81fbfc4
--- /dev/null
+++ b/kimap/tests/listjobtest.cpp
@@ -0,0 +1,237 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/listjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+Q_DECLARE_METATYPE(QList<KIMAP::MailBoxDescriptor>)
+Q_DECLARE_METATYPE(QList< QList<QByteArray> >)
+
+class ListJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testList_data() {
+ QTest::addColumn<bool>( "unsubscribed" );
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+ QTest::addColumn<QList<KIMAP::MailBoxDescriptor> >( "listresult" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 LIST \"\" *"
+ << "S: * LIST ( \\HasChildren ) / INBOX"
+ << "S: * LIST ( \\HasNoChildren ) / INBOX/&AOQ- &APY- &APw- @ &IKw-"
+ << "S: * LIST ( \\HasChildren ) / INBOX/lost+found"
+ << "S: * LIST ( \\HasNoChildren ) / \"INBOX/lost+found/Calendar Public-20080128\""
+ << "S: A000001 OK LIST completed";
+ KIMAP::MailBoxDescriptor descriptor;
+ QList<KIMAP::MailBoxDescriptor> listresult;
+
+ descriptor.separator = '/';
+ descriptor.name = "INBOX";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = QString::fromUtf8( "INBOX/ä ö ü @ €" );
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/lost+found";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/lost+found/Calendar Public-20080128";
+ listresult << descriptor;
+
+ QTest::newRow( "normal" ) << true << scenario << listresult;
+
+ scenario.clear();;
+ scenario << FakeServer::preauth()
+ << "C: A000001 LIST \"\" *"
+ << "S: * LIST ( \\HasChildren ) / Inbox"
+ << "S: * LIST ( \\HasNoChildren ) / Inbox/&AOQ- &APY- &APw- @ &IKw-"
+ << "S: * LIST ( \\HasChildren ) / Inbox/lost+found"
+ << "S: * LIST ( \\HasNoChildren ) / \"Inbox/lost+found/Calendar Public-20080128\""
+ << "S: A000001 OK LIST completed";
+ listresult.clear();
+
+ descriptor.separator = '/';
+ descriptor.name = "INBOX";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = QString::fromUtf8( "INBOX/ä ö ü @ €" );
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/lost+found";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/lost+found/Calendar Public-20080128";
+ listresult << descriptor;
+
+ QTest::newRow( "lowercase Inbox" ) << true << scenario << listresult;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 LSUB \"\" *"
+ << "S: * LSUB ( \\HasChildren ) / INBOX"
+ << "S: * LSUB ( ) / INBOX/Calendar/3196"
+ << "S: * LSUB ( \\HasChildren ) / INBOX/Calendar/ff"
+ << "S: * LSUB ( ) / INBOX/Calendar/ff/hgh"
+ << "S: * LSUB ( ) / user/test2/Calendar"
+ << "S: A000001 OK LSUB completed";
+ listresult.clear();
+
+ descriptor.separator = '/';
+ descriptor.name = "INBOX";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/Calendar/3196";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/Calendar/ff";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/Calendar/ff/hgh";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "user/test2/Calendar";
+ listresult << descriptor;
+
+ QTest::newRow( "subscribed" ) << false << scenario << listresult;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 LSUB \"\" *"
+ << "S: * LSUB ( \\HasChildren ) / Inbox"
+ << "S: * LSUB ( ) / Inbox/Calendar/3196"
+ << "S: * LSUB ( \\HasChildren ) / Inbox/Calendar/ff"
+ << "S: * LSUB ( ) / Inbox/Calendar/ff/hgh"
+ << "S: * LSUB ( ) / user/test2/Calendar"
+ << "S: A000001 OK LSUB completed";
+ listresult.clear();
+
+ descriptor.separator = '/';
+ descriptor.name = "INBOX";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/Calendar/3196";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/Calendar/ff";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/Calendar/ff/hgh";
+ listresult << descriptor;
+ descriptor.separator = '/';
+ descriptor.name = "user/test2/Calendar";
+ listresult << descriptor;
+
+ QTest::newRow( "subscribed, lowercase Inbox" ) << false << scenario << listresult;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 LIST \"\" *"
+ << "S: * LIST ( \\HasNoChildren ) / INBOX/lost+found/Calendar Public-20080128"
+ << "S: A000001 OK LIST completed";
+ listresult.clear();
+ descriptor.separator = '/';
+ descriptor.name = "INBOX/lost+found/Calendar Public-20080128";
+ listresult << descriptor;
+
+ QTest::newRow( "unquoted-space" ) << true << scenario << listresult;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 LIST \"\" *"
+ << "S: * LIST ( \\NoInferiors ) ( ) INBOX"
+ << "S: A000001 OK LIST completed";
+ listresult.clear();
+ descriptor.separator = '/';
+ descriptor.name = "INBOX";
+ listresult << descriptor;
+
+ QTest::newRow( "separator is empty list" ) << true << scenario << listresult;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 LIST \"\" *"
+ << "S: A000001 BAD command unknown or arguments invalid";
+ listresult.clear();
+ QTest::newRow( "bad" ) << true << scenario << listresult;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 LIST \"\" *"
+ << "S: A000001 NO list failure";
+ QTest::newRow( "no" ) << true << scenario << listresult;
+}
+
+void testList()
+{
+ QFETCH( bool, unsubscribed);
+ QFETCH( QList<QByteArray>, scenario );
+ QFETCH( QList<KIMAP::MailBoxDescriptor>, listresult );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::ListJob *job = new KIMAP::ListJob(&session);
+ job->setIncludeUnsubscribed(unsubscribed);
+
+ QSignalSpy spy( job,
+ SIGNAL(mailBoxesReceived(const QList<KIMAP::MailBoxDescriptor>&,
+ const QList< QList<QByteArray> >&)) );
+
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD response", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO response", Continue);
+ QVERIFY(result);
+ if (result) {
+ QVERIFY( spy.count()>0 );
+ QList<KIMAP::MailBoxDescriptor> mailBoxes;
+
+ for ( int i=0; i<spy.count(); i++ ) {
+ mailBoxes+= spy.at( i ).at( 0 ).value< QList<KIMAP::MailBoxDescriptor> >();
+ }
+
+ //kDebug() << mailBoxes.first().name;
+ //kDebug() << listresult.first().name;
+ QCOMPARE(mailBoxes, listresult);
+ }
+// QCOMPARE(job->mailBox(), mailbox);
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( ListJobTest )
+
+#include "listjobtest.moc"
diff --git a/kimap/tests/loginjobtest.cpp b/kimap/tests/loginjobtest.cpp
new file mode 100644
index 0000000..f937f5b
--- /dev/null
+++ b/kimap/tests/loginjobtest.cpp
@@ -0,0 +1,154 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/loginjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class LoginJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void shouldHandleLogin_data()
+{
+ QTest::addColumn<QString>( "user" );
+ QTest::addColumn<QString>( "password" );
+ QTest::addColumn< QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::greeting()
+ << "C: A000001 LOGIN \"user\" \"password\""
+ << "S: A000001 OK User logged in";
+
+ QTest::newRow( "success" ) << "user" << "password" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::greeting()
+ << "C: A000001 LOGIN \"user_bad\" \"password\""
+ << "S: A000001 NO Login failed: authentication failure";
+
+ QTest::newRow( "wrong login" ) << "user_bad" << "password" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::greeting()
+ << "C: A000001 LOGIN \"user\" \"aa\\\"bb\\\\cc[dd ee\""
+ << "S: A000001 OK User logged in";
+
+ QTest::newRow( "special chars" ) << "user" << "aa\"bb\\cc[dd ee" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth();
+
+ QTest::newRow( "already authenticated" ) << "user" << "password" << scenario;
+}
+
+void shouldHandleLogin()
+{
+ QFETCH( QString, user );
+ QFETCH( QString, password );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989);
+
+ KIMAP::LoginJob *login = new KIMAP::LoginJob(session);
+ login->setUserName(user);
+ login->setPassword(password);
+ bool result = login->exec();
+
+ QEXPECT_FAIL("wrong login","Login with bad user name", Continue);
+ QEXPECT_FAIL("already authenticated","Trying to log on an already authenticated session", Continue);
+ QVERIFY(result);
+
+ fakeServer.quit();
+ delete session;
+}
+
+void shouldSaveServerGreeting_data()
+{
+ QTest::addColumn<QString>( "greeting" );
+ QTest::addColumn< QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::greeting()
+ << "C: A000001 LOGIN \"user\" \"password\""
+ << "S: A000001 OK Welcome John Smith";
+
+ QTest::newRow( "greeting" ) << "Welcome John Smith" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::greeting()
+ << "C: A000001 LOGIN \"user\" \"password\""
+ << "S: A000001 OK Welcome John Smith (last login: Feb 21, 2010)";
+
+ QTest::newRow( "greeting with parenthesis" ) << "Welcome John Smith (last login: Feb 21, 2010)" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::greeting()
+ << "C: A000001 LOGIN \"user\" \"password\""
+ << "S: A000001 OK";
+
+ QTest::newRow( "no greeting" ) << "" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::greeting()
+ << "C: A000001 LOGIN \"user\" \"password\""
+ << "S: A000001 NO Login failed: authentication failure";
+
+ QTest::newRow( "login failed" ) << "" << scenario;
+}
+
+void shouldSaveServerGreeting()
+{
+ QFETCH( QString, greeting );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989);
+
+ KIMAP::LoginJob *login = new KIMAP::LoginJob(session);
+ login->setUserName("user");
+ login->setPassword("password");
+ login->exec();
+
+ QCOMPARE(login->serverGreeting(), greeting);
+
+ fakeServer.quit();
+ delete session;
+}
+};
+
+QTEST_KDEMAIN_CORE( LoginJobTest )
+
+#include "loginjobtest.moc"
diff --git a/kimap/tests/logoutjobtest.cpp b/kimap/tests/logoutjobtest.cpp
new file mode 100644
index 0000000..9028265
--- /dev/null
+++ b/kimap/tests/logoutjobtest.cpp
@@ -0,0 +1,81 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/logoutjob.h"
+#include "kimap/loginjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class LogoutJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testLogout()
+{
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::preauth()
+ << "C: A000001 LOGOUT"
+ << "S: A000001 OK LOGOUT completed"
+ );
+ fakeServer.startAndWait();
+
+ KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989);
+
+ KIMAP::LogoutJob *logout = new KIMAP::LogoutJob(session);
+ QVERIFY(logout->exec());
+
+ fakeServer.quit();
+ delete session;
+}
+
+void testLogoutUntagged()
+{
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::preauth()
+ << "C: A000001 LOGOUT"
+ << "S: * some untagged response"
+ << "S: A000001 OK LOGOUT completed"
+ );
+ fakeServer.startAndWait();
+
+ KIMAP::Session *session = new KIMAP::Session("127.0.0.1", 5989);
+
+ KIMAP::LogoutJob *logout = new KIMAP::LogoutJob(session);
+ QVERIFY(logout->exec());
+
+ fakeServer.quit();
+ delete session;
+}
+
+};
+
+QTEST_KDEMAIN_CORE( LogoutJobTest )
+
+#include "logoutjobtest.moc"
diff --git a/kimap/tests/quotarootjobtest.cpp b/kimap/tests/quotarootjobtest.cpp
new file mode 100644
index 0000000..5cf5a4f
--- /dev/null
+++ b/kimap/tests/quotarootjobtest.cpp
@@ -0,0 +1,187 @@
+/*
+ Copyright (C) 2011 Andras Mantia <amantia@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/getquotarootjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+Q_DECLARE_METATYPE( QList<qint64> );
+
+class QuotaRootJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testGetQuotaRoot_data() {
+ QTest::addColumn<QString>("mailbox");
+ QTest::addColumn<QList<QByteArray> >( "roots" );
+ QTest::addColumn<QList<QByteArray> >( "resources" );
+ QTest::addColumn<QList<qint64> >( "usages" );
+ QTest::addColumn<QList<qint64> >( "limits" );
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> roots;
+ QList<QByteArray> resources;
+ QList<qint64> usages;
+ QList<qint64> limits;
+ QList<QByteArray> scenario;
+ roots << "";
+ resources << "STORAGE";
+ limits << 512;
+ usages << 10;
+ scenario << FakeServer::preauth()
+ << "C: A000001 GETQUOTAROOT \"INBOX\""
+ << "S: * QUOTAROOT INBOX \"\" "
+ << "S: * QUOTA \"\" (STORAGE 10 512)"
+ << "S: A000001 OK GETQUOTA completed";
+ QTest::newRow( "one root, one resource" ) << "INBOX" << roots << resources << usages << limits << scenario;
+
+ roots.clear();
+ resources.clear();
+ usages.clear();
+ limits.clear();
+ scenario.clear();
+ roots << "";
+ resources << "STORAGE" << "MESSAGE";
+ usages << 10 << 8221;
+ limits << 512 << 20000;
+ scenario << FakeServer::preauth()
+ << "C: A000001 GETQUOTAROOT \"INBOX\""
+ << "S: * QUOTAROOT INBOX \"\" "
+ << "S: * QUOTA \"\" (STORAGE 10 512)"
+ << "S: * QUOTA \"\" ( MESSAGE 8221 20000 ) "
+ << "S: A000001 OK GETQUOTA completed";
+ QTest::newRow( "one root, multiple resource" ) << "INBOX" << roots << resources << usages << limits << scenario;
+
+ roots.clear();
+ resources.clear();
+ usages.clear();
+ limits.clear();
+ scenario.clear();
+ roots << "root1" << "root2";
+ resources << "STORAGE" << "MESSAGE";
+ usages << 10 << 8221 << 30 << 100;
+ limits << 512 << 20000 << 5124 << 120000;
+ scenario << FakeServer::preauth()
+ << "C: A000001 GETQUOTAROOT \"INBOX\""
+ << "S: * QUOTAROOT INBOX \"root1\" root2 "
+ << "S: * QUOTA \"root1\" (STORAGE 10 512)"
+ << "S: * QUOTA \"root1\" ( MESSAGE 8221 20000 ) "
+ << "S: * QUOTA \"root2\" ( MESSAGE 100 120000 ) "
+ << "S: * QUOTA \"root2\" (STORAGE 30 5124)"
+ << "S: A000001 OK GETQUOTA completed";
+ QTest::newRow( "multiple roots, multiple resource" ) << "INBOX" << roots << resources << usages << limits << scenario;
+
+ roots.clear();
+ resources.clear();
+ usages.clear();
+ limits.clear();
+ scenario.clear();
+ roots << "";
+ resources << "STORAGE" << "MESSAGE";
+ usages << 10 << 8221;
+ limits << 512 << 20000;
+ scenario << FakeServer::preauth()
+ << "C: A000001 GETQUOTAROOT \"INBOX\""
+ << "S: * QUOTAROOT INBOX"
+ << "S: * QUOTA (STORAGE 10 512)"
+ << "S: * QUOTA ( MESSAGE 8221 20000 ) "
+ << "S: A000001 OK GETQUOTA completed";
+ QTest::newRow( "no rootname, multiple resource" ) << "INBOX" << roots << resources << usages << limits << scenario;
+
+ roots.clear();
+ resources.clear();
+ usages.clear();
+ limits.clear();
+ scenario.clear();
+ roots << "";
+ resources << "STORAGE";
+ limits << 512;
+ usages << 10;
+ scenario << FakeServer::preauth()
+ << "C: A000001 GETQUOTAROOT \"INBOX\""
+ << "S: * QUOTAROOT INBOX \"\" "
+ << "S: * QUOTA (STORAGE 10 512)"
+ << "S: A000001 OK GETQUOTA completed";
+ QTest::newRow( "no rootname in QUOTA, one resource" ) << "INBOX" << roots << resources << usages << limits << scenario;
+
+ roots.clear();
+ resources.clear();
+ usages.clear();
+ limits.clear();
+ scenario.clear();
+ roots << "";
+ resources << "STORAGE";
+ limits << 512;
+ usages << 10;
+ scenario << FakeServer::preauth()
+ << "C: A000001 GETQUOTAROOT \"INBOX\""
+ << "S: * QUOTAROOT INBOX \"root1\" "
+ << "S: * QUOTA \"root2\" (STORAGE 10 512)"
+ << "S: A000001 OK GETQUOTA completed";
+ QTest::newRow( "rootname missmatch" ) << "INBOX" << roots << resources << usages << limits << scenario;
+}
+
+void testGetQuotaRoot()
+{
+ QFETCH( QString, mailbox );
+ QFETCH( QList<QByteArray>, roots);
+ QFETCH( QList<QByteArray>, resources);
+ QFETCH( QList<qint64>, usages);
+ QFETCH( QList<qint64>, limits);
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::GetQuotaRootJob *job = new KIMAP::GetQuotaRootJob(&session);
+ job->setMailBox(mailbox);
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD response", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO response", Continue);
+ QVERIFY(result);
+ QEXPECT_FAIL("rootname missmatch" , "Expected failure on rootname missmatch in QUOTAROOT and QUOTA response", Abort);
+ QCOMPARE(job->roots(), roots);
+ for( int rootIdx = 0; rootIdx < roots.size() ;rootIdx++ ) {
+ const QByteArray& root = roots[rootIdx];
+ for( int i = 0; i < resources.size(); i++ ) {
+ int idx = i + rootIdx * roots.size();
+ QByteArray resource = resources[i];
+ QCOMPARE(job->limit(root, resource), limits[idx] );
+ QCOMPARE(job->usage(root, resource), usages[idx] );
+ }
+ }
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( QuotaRootJobTest )
+
+#include "quotarootjobtest.moc"
diff --git a/kimap/tests/renamejobtest.cpp b/kimap/tests/renamejobtest.cpp
new file mode 100644
index 0000000..a139a24
--- /dev/null
+++ b/kimap/tests/renamejobtest.cpp
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/renamejob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class RenameJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testRename_data() {
+ QTest::addColumn<QString>( "mailbox" );
+ QTest::addColumn<QString>( "newname" );
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 RENAME \"INBOX\" \"oldmail\""
+ << "S: A000001 OK RENAME completed";
+ QTest::newRow( "good" ) << "INBOX" << "oldmail" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 RENAME \"INBOX-FAIL-BAD\" \"oldmail-bad\""
+ << "S: A000001 BAD command unknown or arguments invalid";
+ QTest::newRow( "bad" ) << "INBOX-FAIL-BAD" << "oldmail-bad" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 RENAME \"INBOX-FAIL-NO\" \"oldmail-no\""
+ << "S: A000001 NO rename failure";
+ QTest::newRow( "no" ) << "INBOX-FAIL-NO" << "oldmail-no" << scenario;
+}
+
+void testRename()
+{
+ QFETCH( QString, mailbox );
+ QFETCH( QString, newname );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::RenameJob *job = new KIMAP::RenameJob(&session);
+ job->setSourceMailBox(mailbox);
+ job->setDestinationMailBox(newname);
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD response", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO response", Continue);
+ QVERIFY(result);
+ QCOMPARE(job->sourceMailBox(), mailbox);
+ QCOMPARE(job->destinationMailBox(), newname);
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( RenameJobTest )
+
+#include "renamejobtest.moc"
diff --git a/kimap/tests/selectjobtest.cpp b/kimap/tests/selectjobtest.cpp
new file mode 100644
index 0000000..9ee2a80
--- /dev/null
+++ b/kimap/tests/selectjobtest.cpp
@@ -0,0 +1,147 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/loginjob.h"
+#include "kimap/session.h"
+#include "kimap/selectjob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class SelectJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testSingleSelect_data() {
+ QTest::addColumn<QList<QByteArray> >( "scenario" );
+ QTest::addColumn<QList<QByteArray> >( "flags" );
+ QTest::addColumn<QList<QByteArray> >( "permanentflags" );
+ QTest::addColumn<int>( "messagecount" );
+ QTest::addColumn<int>( "recentcount" );
+ QTest::addColumn<int>( "firstUnseenIndex" );
+ QTest::addColumn<qint64>( "uidValidity" );
+ QTest::addColumn<qint64>( "nextUid" );
+
+ QList<QByteArray> scenario;
+ QList<QByteArray> flags;
+ QList<QByteArray> permanentflags;
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX\""
+ << "S: * 172 EXISTS"
+ << "S: * 1 RECENT"
+ << "S: * OK [UNSEEN 12] Message 12 is first unseen"
+ << "S: * OK [UIDVALIDITY 3857529045] UIDs valid"
+ << "S: * OK [UIDNEXT 4392] Predicted next UID"
+ << "S: * FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)"
+ << "S: * OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited"
+ << "S: A000001 OK [READ-WRITE] SELECT completed";
+
+ flags << "\\Answered" << "\\Flagged" << "\\Deleted" << "\\Seen" << "\\Draft";
+ permanentflags << "\\Deleted" << "\\Seen" << "\\*";
+ QTest::newRow( "good" ) << scenario << flags << permanentflags << 172 << 1 << 12 << (qint64)3857529045 << (qint64)4392;
+
+ scenario.clear();
+ flags.clear();
+ permanentflags.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX\""
+ << "S: A000001 BAD command unknown or arguments invalid";
+ QTest::newRow( "bad" ) << scenario << flags << permanentflags << 0 << 0 << 0 << (qint64)0 << (qint64)0;
+
+ scenario.clear();
+ flags.clear();
+ permanentflags.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX\""
+ << "S: A000001 NO select failure";
+ QTest::newRow( "no" ) << scenario << flags << permanentflags << 0 << 0 << 0 << (qint64)0 << (qint64)0;
+}
+
+void testSingleSelect()
+{
+ QFETCH( QList<QByteArray>, scenario );
+ QFETCH( QList<QByteArray>, flags );
+ QFETCH( QList<QByteArray>, permanentflags );
+ QFETCH( int, messagecount);
+ QFETCH( int, recentcount);
+ QFETCH( int, firstUnseenIndex);
+ QFETCH( qint64, uidValidity);
+ QFETCH( qint64, nextUid);
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::SelectJob *job = new KIMAP::SelectJob(&session);
+ job->setMailBox("INBOX");
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD scenario", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO scenario", Continue);
+ QVERIFY(result);
+ if (result) {
+ QCOMPARE(job->flags(), flags);
+ QCOMPARE(job->permanentFlags(), permanentflags);
+ QCOMPARE(job->messageCount(), messagecount);
+ QCOMPARE(job->recentCount(), recentcount);
+ QCOMPARE(job->firstUnseenIndex(), firstUnseenIndex);
+ QCOMPARE(job->uidValidity(), uidValidity);
+ QCOMPARE(job->nextUid(), nextUid);
+ }
+
+ fakeServer.quit();
+}
+
+void testSeveralSelect()
+{
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::preauth()
+ << "C: A000001 SELECT \"INBOX\""
+ << "S: A000001 OK [READ-WRITE] SELECT completed"
+ << "C: A000002 SELECT \"INBOX/Foo\""
+ << "S: A000002 OK [READ-WRITE] SELECT completed"
+ );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::SelectJob *job = new KIMAP::SelectJob(&session);
+ job->setMailBox("INBOX");
+ QVERIFY(job->exec());
+
+ job = new KIMAP::SelectJob(&session);
+ job->setMailBox("INBOX/Foo");
+ QVERIFY(job->exec());
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( SelectJobTest )
+
+#include "selectjobtest.moc"
diff --git a/kimap/tests/storejobtest.cpp b/kimap/tests/storejobtest.cpp
new file mode 100644
index 0000000..fca7691
--- /dev/null
+++ b/kimap/tests/storejobtest.cpp
@@ -0,0 +1,100 @@
+/*
+ Copyright (C) 2009 Kevin Ottens <ervin@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/storejob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class StoreJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testStore_data() {
+ QTest::addColumn<bool>( "uidBased" );
+ QTest::addColumn<qint64>( "id" );
+ QTest::addColumn<qint64>( "uid" );
+ QTest::addColumn< QList<QByteArray> >( "flags" );
+ QTest::addColumn< QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 STORE 3 FLAGS (\\Seen \\Foo)"
+ << "S: * 3 FETCH (FLAGS (\\Seen \\Foo) UID 1096)"
+ << "S: A000001 OK STORE completed";
+
+ QTest::newRow( "not uid based" ) << false << qint64(3) << qint64(1096)
+ << ( QList<QByteArray>() << "\\Seen" << "\\Foo" )
+ << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 UID STORE 1096 FLAGS (\\Seen \\Foo)"
+ << "S: * 3 FETCH (FLAGS (\\Seen \\Foo) UID 1096)"
+ << "S: A000001 OK STORE completed";
+
+ QTest::newRow( "uid based" ) << true << qint64(3) << qint64(1096)
+ << ( QList<QByteArray>() << "\\Seen" << "\\Foo" )
+ << scenario;
+}
+
+void testStore()
+{
+ QFETCH( bool, uidBased );
+ QFETCH( qint64, id );
+ QFETCH( qint64, uid );
+ QFETCH( QList<QByteArray>, flags );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::StoreJob *job = new KIMAP::StoreJob(&session);
+ job->setUidBased( uidBased );
+ job->setSequenceSet( KIMAP::ImapSet( uidBased ? uid : id ) );
+ job->setFlags( flags );
+ job->setMode( KIMAP::StoreJob::SetFlags );
+ bool result = job->exec();
+ QVERIFY(result);
+ if ( uidBased ) {
+ QVERIFY( job->resultingFlags().contains( uid ) );
+ } else {
+ QVERIFY( job->resultingFlags().contains( id ) );
+ }
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( StoreJobTest )
+
+#include "storejobtest.moc"
diff --git a/kimap/tests/subscribejobtest.cpp b/kimap/tests/subscribejobtest.cpp
new file mode 100644
index 0000000..f314345
--- /dev/null
+++ b/kimap/tests/subscribejobtest.cpp
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/subscribejob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class SubscribeJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testSubscribe_data() {
+ QTest::addColumn<QString>( "mailbox" );
+ QTest::addColumn< QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 SUBSCRIBE \"INBOX/foo\""
+ << "S: A000001 OK CREATE completed";
+ QTest::newRow( "good" ) << "INBOX/foo" << scenario ;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SUBSCRIBE \"INBOX-FAIL-BAD\""
+ << "S: A000001 BAD command unknown or arguments invalid";
+ QTest::newRow( "bad" ) << "INBOX-FAIL-BAD" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 SUBSCRIBE \"INBOX-FAIL-NO\""
+ << "S: A000001 NO subscribe failure";
+ QTest::newRow( "no" ) << "INBOX-FAIL-NO" << scenario ;
+}
+
+void testSubscribe()
+{
+ QFETCH( QString, mailbox );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::SubscribeJob *job = new KIMAP::SubscribeJob(&session);
+ job->setMailBox(mailbox);
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD scenario", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO scenario", Continue);
+ QVERIFY(result);
+ QCOMPARE(job->mailBox(), mailbox);
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( SubscribeJobTest )
+
+#include "subscribejobtest.moc"
diff --git a/kimap/tests/testimapidle.cpp b/kimap/tests/testimapidle.cpp
new file mode 100644
index 0000000..bdf8d69
--- /dev/null
+++ b/kimap/tests/testimapidle.cpp
@@ -0,0 +1,130 @@
+/**
+ * This file is part of the KDE project
+ * Copyright (C) 2009 Kevin Ottens <ervin@kde.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <kcomponentdata.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <qtcpsocket.h>
+#include <QCoreApplication>
+#include <qsignalspy.h>
+
+#include "kimap/session.h"
+#include "kimap/capabilitiesjob.h"
+#include "kimap/idlejob.h"
+#include "kimap/loginjob.h"
+#include "kimap/logoutjob.h"
+#include "kimap/selectjob.h"
+#include "kimap/closejob.h"
+#include "kimap/sessionuiproxy.h"
+
+using namespace KIMAP;
+
+class UiProxy: public SessionUiProxy {
+ public:
+ bool ignoreSslError(const KSslErrorUiData& errorData) {
+ Q_UNUSED( errorData );
+ return true;
+ }
+};
+
+int main( int argc, char **argv )
+{
+ KAboutData about("TestImapIdle", 0, ki18n("TestImapIdle"), "version");
+ KComponentData cData(&about);
+
+ if (argc < 4) {
+ kError() << "Not enough parameters, expecting: <server> <user> <password>";
+ }
+
+ QString server = QString::fromLocal8Bit(argv[1]);
+ int port = 143;
+ if ( server.count( ':' ) == 1 ) {
+ port = server.split( ':' ).last().toInt();
+ server = server.split( ':' ).first();
+ }
+ QString user = QString::fromLocal8Bit(argv[2]);
+ QString password = QString::fromLocal8Bit(argv[3]);
+
+ kDebug() << "Listening:" << server << port << user << password;
+ qDebug();
+
+ QCoreApplication app(argc, argv);
+ Session session(server, port);
+ UiProxy *proxy = new UiProxy();
+ session.setUiProxy(UiProxy::Ptr( proxy ));
+
+ kDebug() << "Logging in...";
+ LoginJob *login = new LoginJob(&session);
+ login->setUserName(user);
+ login->setPassword(password);
+ login->exec();
+ Q_ASSERT_X(login->error()==0, "LoginJob", login->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Authenticated);
+ qDebug();
+
+ kDebug() << "Asking for capabilities:";
+ CapabilitiesJob *capabilities = new CapabilitiesJob(&session);
+ capabilities->exec();
+ Q_ASSERT_X(capabilities->error()==0, "CapabilitiesJob", capabilities->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Authenticated);
+ kDebug() << capabilities->capabilities();
+ qDebug();
+
+ Q_ASSERT(capabilities->capabilities().contains("IDLE"));
+
+ kDebug() << "Selecting INBOX:";
+ SelectJob *select = new SelectJob(&session);
+ select->setMailBox("INBOX");
+ select->exec();
+ Q_ASSERT_X(select->error()==0, "SelectJob", select->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Selected);
+ kDebug() << "Flags:" << select->flags();
+ kDebug() << "Permanent flags:" << select->permanentFlags();
+ kDebug() << "Total Number of Messages:" << select->messageCount();
+ kDebug() << "Number of recent Messages:" << select->recentCount();
+ kDebug() << "First Unseen Message Index:" << select->firstUnseenIndex();
+ kDebug() << "UID validity:" << select->uidValidity();
+ kDebug() << "Next UID:" << select->nextUid();
+ qDebug();
+
+ kDebug() << "Start idling...";
+ IdleJob *idle = new IdleJob(&session);
+ QObject::connect(idle, SIGNAL(mailBoxStats(KIMAP::IdleJob*,QString,int,int)),
+ idle, SLOT(stop()));
+ idle->exec();
+ kDebug() << "Idling done for" << idle->lastMailBox()
+ << "message count:" << idle->lastMessageCount()
+ << "recent count:" << idle->lastRecentCount();
+
+ kDebug() << "Closing INBOX:";
+ CloseJob *close = new CloseJob(&session);
+ close->exec();
+ Q_ASSERT(session.state()==Session::Authenticated);
+ qDebug();
+
+ kDebug() << "Logging out...";
+ LogoutJob *logout = new LogoutJob(&session);
+ logout->exec();
+ Q_ASSERT_X(logout->error()==0, "LogoutJob", logout->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Disconnected);
+
+ return 0;
+}
diff --git a/kimap/tests/testimapserver.cpp b/kimap/tests/testimapserver.cpp
new file mode 100644
index 0000000..c22aee2
--- /dev/null
+++ b/kimap/tests/testimapserver.cpp
@@ -0,0 +1,604 @@
+/**
+ * This file is part of the KDE project
+ * Copyright (C) 2009 Kevin Ottens <ervin@kde.org>
+ * Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <kcomponentdata.h>
+#include <kaboutdata.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <qtcpsocket.h>
+#include <QCoreApplication>
+#include <qsignalspy.h>
+
+#include "kimap/acl.h"
+#include "kimap/session.h"
+#include "kimap/appendjob.h"
+#include "kimap/capabilitiesjob.h"
+#include "kimap/fetchjob.h"
+#include "kimap/listjob.h"
+#include "kimap/loginjob.h"
+#include "kimap/logoutjob.h"
+#include "kimap/selectjob.h"
+#include "kimap/closejob.h"
+#include "kimap/expungejob.h"
+#include "kimap/createjob.h"
+#include "kimap/deletejob.h"
+#include "kimap/namespacejob.h"
+#include "kimap/subscribejob.h"
+#include "kimap/unsubscribejob.h"
+#include "kimap/renamejob.h"
+#include "kimap/storejob.h"
+#include "kimap/sessionuiproxy.h"
+#include "kimap/setacljob.h"
+#include "kimap/getacljob.h"
+#include "kimap/deleteacljob.h"
+#include "kimap/myrightsjob.h"
+#include "kimap/listrightsjob.h"
+#include "kimap/setmetadatajob.h"
+#include "kimap/getmetadatajob.h"
+
+using namespace KIMAP;
+
+class UiProxy: public SessionUiProxy {
+ public:
+ bool ignoreSslError(const KSslErrorUiData& errorData) {
+ return true;
+ }
+};
+
+void dumpContentHelper(KMime::Content *part, const QString &partId = QString())
+{
+ if (partId.isEmpty()) {
+ kDebug() << "** Message root **";
+ } else {
+ kDebug() << "** Part" << partId << "**";
+ }
+
+ kDebug() << part->head();
+
+ KMime::Content::List children = part->contents();
+ for (int i=0; i<children.size(); i++) {
+ QString newId = partId;
+ if (!newId.isEmpty()) {
+ newId+=".";
+ }
+ newId+=QString::number(i+1);
+ dumpContentHelper(children[i], newId);
+ }
+}
+
+void listFolders(Session *session, bool includeUnsubscribed = false, const QString &nameFilter = "")
+{
+ ListJob *list = new ListJob(session);
+ list->setIncludeUnsubscribed(includeUnsubscribed);
+ list->exec();
+ Q_ASSERT_X(list->error()==0, "ListJob", list->errorString().toLocal8Bit());
+ int count = list->mailBoxes().size();
+ for (int i=0; i<count; ++i) {
+ MailBoxDescriptor descriptor = list->mailBoxes()[i];
+ if (descriptor.name.endsWith(nameFilter)) {
+ kDebug() << descriptor.separator << descriptor.name;
+ }
+ }
+
+}
+
+void testMetaData(Session *session)
+{
+ kDebug() << "TESTING: METADATA commands";
+ CreateJob *create = new CreateJob(session);
+ create->setMailBox("INBOX/TestFolder");
+ create->exec();
+
+ SetMetaDataJob *setmetadata = new SetMetaDataJob(session);
+ setmetadata->setMailBox("INBOX/TestFolder");
+ setmetadata->setServerCapability(SetMetaDataJob::Annotatemore);
+ setmetadata->setEntry("/comment");
+ setmetadata->addMetaData("value.priv", "My new comment");
+ setmetadata->exec();
+
+ setmetadata = new SetMetaDataJob(session);
+ setmetadata->setMailBox("INBOX/TestFolder");
+ setmetadata->setServerCapability(SetMetaDataJob::Annotatemore);
+ setmetadata->setEntry("/check");
+ setmetadata->addMetaData("value.priv", "true");
+ setmetadata->exec();
+
+ GetMetaDataJob *getmetadata = new GetMetaDataJob(session);
+ getmetadata->setMailBox("INBOX/TestFolder");
+ getmetadata->setServerCapability(SetMetaDataJob::Annotatemore);
+ getmetadata->addEntry("/*","value.priv");
+ getmetadata->exec();
+ Q_ASSERT_X(getmetadata->metaData("INBOX/TestFolder", "/check", "value.priv") == "true", "", "/check metadata should be true");
+ Q_ASSERT_X(getmetadata->metaData("INBOX/TestFolder", "/comment", "value.priv") == "My new comment", "", "/check metadata should be My new comment");
+
+ //cleanup
+ DeleteJob *deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/TestFolder");
+ deletejob->exec();
+}
+
+void testAcl(Session *session, const QString &user)
+{
+ kDebug() << "TESTING: ACL commands";
+ CreateJob *create = new CreateJob(session);
+ create->setMailBox("INBOX/TestFolder");
+ create->exec();
+
+ ListRightsJob *listRights = new ListRightsJob(session);
+ listRights->setMailBox("INBOX/TestFolder");
+ listRights->setIdentifier(user.toLatin1());
+ listRights->exec();
+ kDebug() << "Default rights on INBOX/TestFolder: " << Acl::rightsToString(listRights->defaultRights());
+ QList<Acl::Rights> possible = listRights->possibleRights();
+ QStringList strList;
+ Q_FOREACH(Acl::Rights r, possible) {
+ strList << Acl::rightsToString(r);
+ }
+ kDebug() << "Possible rights on INBOX/TestFolder: " << strList;
+
+ MyRightsJob *myRights = new MyRightsJob(session);
+ myRights->setMailBox("INBOX/TestFolder");
+ myRights->exec();
+
+ Acl::Rights mine = myRights->rights();
+ kDebug() << "My rights on INBOX/TestFolder: " << Acl::rightsToString(mine);
+ kDebug() << "Reading INBOX/TestFolder is possible: " << myRights->hasRightEnabled(Acl::Read);
+ Q_ASSERT_X(myRights->hasRightEnabled(Acl::Read), "Reading INBOX is NOT possible", "");
+
+ GetAclJob *getAcl= new GetAclJob(session);
+ getAcl->setMailBox("INBOX/TestFolder");
+ getAcl->exec();
+ kDebug() << "Anyone rights on INBOX/TestFolder: " << getAcl->rights("anyone");
+ Acl::Rights users = getAcl->rights(user.toLatin1());
+ kDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users);
+ Q_ASSERT_X(mine == users, "GETACL returns different rights for the same user", "");
+
+
+ kDebug() << "Removing Delete right ";
+ mine = Acl::Delete;
+ SetAclJob *setAcl= new SetAclJob(session);
+ setAcl->setMailBox("INBOX/TestFolder");
+ setAcl->setIdentifier(user.toLatin1());
+ setAcl->setRights(AclJobBase::Remove, mine);
+ setAcl->exec();
+
+ getAcl= new GetAclJob(session);
+ getAcl->setMailBox("INBOX/TestFolder");
+ getAcl->exec();
+ users = getAcl->rights(user.toLatin1());
+ kDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users);
+
+ kDebug() << "Adding back Delete right ";
+ mine = Acl::Delete;
+ setAcl= new SetAclJob(session);
+ setAcl->setMailBox("INBOX/TestFolder");
+ setAcl->setIdentifier(user.toLatin1());
+ setAcl->setRights(AclJobBase::Add, mine);
+ setAcl->exec();
+
+ getAcl= new GetAclJob(session);
+ getAcl->setMailBox("INBOX/TestFolder");
+ getAcl->exec();
+ users = getAcl->rights(user.toLatin1());
+ kDebug() << user << " rights on INBOX/TestFolder: " << Acl::rightsToString(users);
+
+ //cleanup
+ DeleteJob *deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/TestFolder");
+ deletejob->exec();
+}
+
+void testAppendAndStore(Session *session)
+{
+ kDebug() << "TESTING: APPEND and STORE";
+ //setup
+ CreateJob *create = new CreateJob(session);
+ create->setMailBox("INBOX/TestFolder");
+ create->exec();
+
+ QByteArray testMailContent =
+ "Date: Mon, 7 Feb 1994 21:52:25 -0800 (PST)\r\n"
+ "From: Fred Foobar <foobar@Blurdybloop.COM>\r\n"
+ "Subject: afternoon meeting\r\n"
+ "To: mooch@owatagu.siam.edu\r\n"
+ "Message-Id: <B27397-0100000@Blurdybloop.COM>\r\n"
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: TEXT/PLAIN; CHARSET=US-ASCII\r\n"
+ "\r\n"
+ "Hello Joe, do you think we can meet at 3:30 tomorrow?\r\n";
+
+ kDebug() << "Append a message in INBOX/TestFolder...";
+ AppendJob *append = new AppendJob(session);
+ append->setMailBox("INBOX/TestFolder");
+ append->setContent(testMailContent);
+ append->exec();
+ Q_ASSERT_X(append->error()==0, "AppendJob", append->errorString().toLocal8Bit());
+
+ kDebug() << "Read the message back and compare...";
+ SelectJob *select = new SelectJob(session);
+ select->setMailBox("INBOX/TestFolder");
+ select->exec();
+
+ FetchJob *fetch = new FetchJob(session);
+ FetchJob::FetchScope scope;
+ fetch->setSequenceSet(ImapSet(1));
+ scope.parts.clear();
+ scope.mode = FetchJob::FetchScope::Content;
+ fetch->setScope(scope);
+ fetch->exec();
+ MessagePtr message = fetch->messages()[1];
+ Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit());
+ testMailContent.replace( "\r\n", "\n" );
+ Q_ASSERT_X(testMailContent==message->head()+"\n"+message->body(),
+ "Message differs from reference", message->head()+"\n"+message->body());
+
+ fetch = new FetchJob(session);
+ fetch->setSequenceSet(ImapSet(1));
+ scope.parts.clear();
+ scope.mode = FetchJob::FetchScope::Flags;
+ fetch->setScope(scope);
+ fetch->exec();
+ MessageFlags expectedFlags = fetch->flags()[1];
+ kDebug() << "Read the message flags:" << expectedFlags;
+
+ kDebug() << "Add the \\Deleted flag...";
+ expectedFlags << "\\Deleted";
+ qSort(expectedFlags);
+ StoreJob *store = new StoreJob(session);
+ store->setSequenceSet(ImapSet(1));
+ store->setMode(StoreJob::AppendFlags);
+ store->setFlags(QList<QByteArray>() << "\\Deleted");
+ store->exec();
+ Q_ASSERT_X(store->error()==0, "StoreJob", store->errorString().toLocal8Bit());
+
+ QList<QByteArray> resultingFlags = store->resultingFlags()[1];
+ qSort(resultingFlags);
+ if (expectedFlags!=resultingFlags) {
+ kDebug() << resultingFlags;
+ }
+ Q_ASSERT(expectedFlags==resultingFlags);
+
+
+ select = new SelectJob(session);
+ select->setMailBox("INBOX");
+ select->exec();
+
+ //cleanup
+ DeleteJob *deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/TestFolder");
+ deletejob->exec();
+ deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/RenamedTestFolder");
+ deletejob->exec();
+}
+
+void testRename(Session *session)
+{
+ kDebug() << "TESTING: RENAME";
+ //setup
+ CreateJob *create = new CreateJob(session);
+ create->setMailBox("INBOX/TestFolder");
+ create->exec();
+
+ kDebug() << "Listing mailboxes with name TestFolder:";
+ listFolders(session, true, "TestFolder");
+
+ //actual tests
+ kDebug() << "Renaming to RenamedTestFolder";
+ RenameJob *rename = new RenameJob(session);
+ rename->setSourceMailBox("INBOX/TestFolder");
+ rename->setDestinationMailBox("INBOX/RenamedTestFolder");
+ rename->exec();
+
+ kDebug() << "Listing mailboxes with name TestFolder:";
+ listFolders(session, true, "TestFolder");
+ kDebug() << "Listing mailboxes with name RenamedTestFolder:";
+ listFolders(session, true, "RenamedTestFolder");
+
+ //cleanup
+ DeleteJob *deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/TestFolder");
+ deletejob->exec();
+ deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/RenamedTestFolder");
+ deletejob->exec();
+}
+
+
+void testSubscribe(Session *session)
+{
+ kDebug() << "TESTING: SUBSCRIBE/UNSUBSCRIBE";
+ //setup
+ CreateJob *create = new CreateJob(session);
+ create->setMailBox("INBOX/TestFolder");
+ create->exec();
+
+ kDebug() << "Listing subscribed mailboxes with name TestFolder:";
+ listFolders(session, false, "TestFolder");
+
+ //actual tests
+ kDebug() << "Subscribing to INBOX/TestFolder";
+ SubscribeJob *subscribe = new SubscribeJob(session);
+ subscribe->setMailBox("INBOX/TestFolder");
+ subscribe->exec();
+
+ kDebug() << "Listing subscribed mailboxes with name TestFolder:";
+ listFolders(session, false, "TestFolder");
+
+ kDebug() << "Unsubscribing from INBOX/TestFolder";
+ UnsubscribeJob *unsubscribe = new UnsubscribeJob(session);
+ unsubscribe->setMailBox("INBOX/TestFolder");
+ unsubscribe->exec();
+
+ kDebug() << "Listing subscribed mailboxes with name TestFolder:";
+ listFolders(session, false, "TestFolder");
+
+ //cleanup
+ DeleteJob *deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/TestFolder");
+ deletejob->exec();
+}
+
+void testDelete(Session *session)
+{
+ kDebug() << "TESTING: DELETE";
+ kDebug() << "Creating INBOX/TestFolder:";
+ CreateJob *create = new CreateJob(session);
+ create->setMailBox("INBOX/TestFolder");
+ create->exec();
+
+
+ kDebug() << "Listing with name TestFolder before DELETE:";
+ listFolders(session, true, "TestFolder");
+
+ kDebug() << "Deleting INBOX/TestFolder";
+ DeleteJob *deletejob = new DeleteJob(session);
+ deletejob->setMailBox("INBOX/TestFolder");
+ deletejob->exec();
+
+ kDebug() << "Listing with name TestFolder after DELETE:";
+ listFolders(session, true, "TestFolder");
+}
+
+int main( int argc, char **argv )
+{
+ KAboutData about("TestImapServer", 0, ki18n("TestImapServer"), "version");
+ KComponentData cData(&about);
+
+ if (argc < 4) {
+ kError() << "Not enough parameters, expecting: <server> <user> <password>";
+ }
+
+ QString server = QString::fromLocal8Bit(argv[1]);
+ int port = 143;
+ if ( server.count( ':' ) == 1 ) {
+ port = server.split( ':' ).last().toInt();
+ server = server.split( ':' ).first();
+ }
+ QString user = QString::fromLocal8Bit(argv[2]);
+ QString password = QString::fromLocal8Bit(argv[3]);
+
+ kDebug() << "Querying:" << server << port << user << password;
+ qDebug();
+
+ QCoreApplication app(argc, argv);
+ Session session(server, port);
+ UiProxy::Ptr proxy( new UiProxy() );
+ session.setUiProxy(proxy);
+
+ kDebug() << "Logging in...";
+ LoginJob *login = new LoginJob(&session);
+ //login->setEncryptionMode(LoginJob::TlsV1);
+ //login->setAuthenticationMode(LoginJob::Plain);
+ login->setUserName(user);
+ login->setPassword(password);
+ login->exec();
+ qDebug();
+
+ /*if (login->encryptionMode() == LoginJob::Unencrypted)
+ {
+ kDebug() << "Encrypted login not possible, try to log in without encryption";
+ login = new LoginJob(&session);
+ login->setUserName(user);
+ login->setPassword(password);
+ login->exec();
+ Q_ASSERT_X(login->error()==0, "LoginJob", login->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Authenticated);
+ qDebug();
+
+ }*/
+
+ kDebug() << "Server greeting:" << session.serverGreeting();
+
+ kDebug() << "Asking for capabilities:";
+ CapabilitiesJob *capabilities = new CapabilitiesJob(&session);
+ capabilities->exec();
+ Q_ASSERT_X(capabilities->error()==0, "CapabilitiesJob", capabilities->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Authenticated);
+ kDebug() << capabilities->capabilities();
+ qDebug();
+
+ kDebug() << "Asking for namespaces:";
+ NamespaceJob *namespaces = new NamespaceJob(&session);
+ namespaces->exec();
+ Q_ASSERT_X(namespaces->error()==0, "CapabilitiesJob", namespaces->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Authenticated);
+
+ kDebug() << "Contains empty namespace:" << namespaces->containsEmptyNamespace();
+
+ kDebug() << "Personal:";
+ foreach ( MailBoxDescriptor ns, namespaces->personalNamespaces() ) {
+ kDebug() << ns.separator << ns.name;
+ }
+
+ kDebug() << "User: ";
+ foreach ( MailBoxDescriptor ns, namespaces->userNamespaces() ) {
+ kDebug() << ns.separator << ns.name;
+ }
+
+ kDebug() << "Shared: ";
+ foreach ( MailBoxDescriptor ns, namespaces->sharedNamespaces() ) {
+ kDebug() << ns.separator << ns.name;
+ }
+ qDebug();
+
+ kDebug() << "Listing mailboxes:";
+ listFolders(&session);
+ Q_ASSERT(session.state()==Session::Authenticated);
+
+ kDebug() << "Selecting INBOX:";
+ SelectJob *select = new SelectJob(&session);
+ select->setMailBox("INBOX");
+ select->exec();
+ Q_ASSERT_X(select->error()==0, "SelectJob", select->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Selected);
+ kDebug() << "Flags:" << select->flags();
+ kDebug() << "Permanent flags:" << select->permanentFlags();
+ kDebug() << "Total Number of Messages:" << select->messageCount();
+ kDebug() << "Number of recent Messages:" << select->recentCount();
+ kDebug() << "First Unseen Message Index:" << select->firstUnseenIndex();
+ kDebug() << "UID validity:" << select->uidValidity();
+ kDebug() << "Next UID:" << select->nextUid();
+ qDebug();
+
+ kDebug() << "Fetching first 3 messages headers:";
+ FetchJob *fetch = new FetchJob(&session);
+ FetchJob::FetchScope scope;
+ fetch->setSequenceSet(ImapSet(1, 3));
+ scope.parts.clear();
+ scope.mode = FetchJob::FetchScope::Headers;
+ fetch->setScope(scope);
+ fetch->exec();
+ Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Selected);
+ QMap<qint64, MessagePtr> messages = fetch->messages();
+ foreach (qint64 id, messages.keys()) {
+ kDebug() << "* Message" << id << "(" << fetch->sizes()[id] << "bytes )";
+ kDebug() << " From :" << messages[id]->from()->asUnicodeString();
+ kDebug() << " To :" << messages[id]->to()->asUnicodeString();
+ kDebug() << " Date :" << messages[id]->date()->asUnicodeString();
+ kDebug() << " Subject :" << messages[id]->subject()->asUnicodeString();
+ kDebug() << " Message-ID:" << messages[id]->messageID()->asUnicodeString();
+ }
+ qDebug();
+
+
+ kDebug() << "Fetching first 3 messages flags:";
+ fetch = new FetchJob(&session);
+ fetch->setSequenceSet(ImapSet(1, 3));
+ scope.parts.clear();
+ scope.mode = FetchJob::FetchScope::Flags;
+ fetch->setScope(scope);
+ fetch->exec();
+ Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Selected);
+ QMap<qint64, MessageFlags> flags = fetch->flags();
+ foreach (qint64 id, flags.keys()) {
+ kDebug() << "* Message" << id << "flags:" << flags[id];
+ }
+ qDebug();
+
+ kDebug() << "Fetching first message structure:";
+ fetch = new FetchJob(&session);
+ fetch->setSequenceSet(ImapSet(1));
+ scope.parts.clear();
+ scope.mode = FetchJob::FetchScope::Structure;
+ fetch->setScope(scope);
+ fetch->exec();
+ Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Selected);
+ MessagePtr message = fetch->messages()[1];
+ dumpContentHelper(message.get());
+ qDebug();
+
+ kDebug() << "Fetching first message second part headers:";
+ fetch = new FetchJob(&session);
+ fetch->setSequenceSet(ImapSet(1));
+ scope.parts.clear();
+ scope.parts << "2";
+ scope.mode = FetchJob::FetchScope::Headers;
+ fetch->setScope(scope);
+ fetch->exec();
+ Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Selected);
+ QMap<qint64, MessageParts> allParts = fetch->parts();
+ foreach (qint64 id, allParts.keys()) {
+ kDebug() << "* Message" << id << "parts headers";
+ MessageParts parts = allParts[id];
+ foreach (const QByteArray &partId, parts.keys()) {
+ kDebug() << " ** Part" << partId;
+ kDebug() << " Name :" << parts[partId]->contentType()->name();
+ kDebug() << " Mimetype :" << parts[partId]->contentType()->mimeType();
+ kDebug() << " Description:" << parts[partId]->contentDescription()->asUnicodeString().simplified();
+ }
+ }
+ qDebug();
+
+ kDebug() << "Fetching first message second part content:";
+ fetch = new FetchJob(&session);
+ fetch->setSequenceSet(ImapSet(1));
+ scope.parts.clear();
+ scope.parts << "2";
+ scope.mode = FetchJob::FetchScope::Content;
+ fetch->setScope(scope);
+ fetch->exec();
+ Q_ASSERT_X(fetch->error()==0, "FetchJob", fetch->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Selected);
+ allParts = fetch->parts();
+ foreach (int id, allParts.keys()) {
+ MessageParts parts = allParts[id];
+ foreach (const QByteArray &partId, parts.keys()) {
+ kDebug() << "* Message" << id << "part" << partId << "content:";
+ kDebug() << parts[partId]->body();
+ }
+ }
+ qDebug();
+
+ testDelete(&session);
+
+ testSubscribe(&session);
+
+ testRename(&session);
+
+ testAppendAndStore(&session);
+
+ testAcl(&session, user);
+
+ testMetaData(&session);
+
+ kDebug() << "Expunge INBOX:";
+ ExpungeJob *expunge = new ExpungeJob(&session);
+ expunge->exec();
+
+ kDebug() << "Closing INBOX:";
+ CloseJob *close = new CloseJob(&session);
+ close->exec();
+ Q_ASSERT(session.state()==Session::Authenticated);
+ qDebug();
+
+ kDebug() << "Logging out...";
+ LogoutJob *logout = new LogoutJob(&session);
+ logout->exec();
+ Q_ASSERT_X(logout->error()==0, "LogoutJob", logout->errorString().toLocal8Bit());
+ Q_ASSERT(session.state()==Session::Disconnected);
+
+ return 0;
+}
diff --git a/kimap/tests/testrfccodecs.cpp b/kimap/tests/testrfccodecs.cpp
new file mode 100644
index 0000000..38abd34
--- /dev/null
+++ b/kimap/tests/testrfccodecs.cpp
@@ -0,0 +1,79 @@
+/*
+ This file is part of the kimap library.
+ Copyright (C) 2007 Tom Albers <tomalbers@kde.nl>
+ Copyright (c) 2007 Allen Winter <winter@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include <qtest_kde.h>
+
+#include "testrfccodecs.h"
+
+QTEST_KDEMAIN_CORE( RFCCodecsTest )
+
+#include "kimap/rfccodecs.h"
+using namespace KIMAP;
+
+void RFCCodecsTest::testIMAPEncoding()
+{
+ QString encoded, decoded;
+ QByteArray bEncoded, bDecoded;
+
+ encoded = encodeImapFolderName( QString::fromUtf8( "Test.Frode Rønning" ) );
+ QCOMPARE( encoded, QString::fromUtf8("Test.Frode R&APg-nning") );
+ bEncoded = encodeImapFolderName( QString::fromUtf8( "Test.Frode Rønning" ).toUtf8() );
+ QCOMPARE( bEncoded, QString::fromUtf8("Test.Frode R&APg-nning").toUtf8() );
+
+ decoded = decodeImapFolderName( QString( "Test.Frode R&APg-nning" ) );
+ QCOMPARE( decoded, QString::fromUtf8("Test.Frode Rønning"));
+ bDecoded = decodeImapFolderName( QString::fromUtf8("Test.Frode Rønning" ).toUtf8() );
+ QCOMPARE( bDecoded, QString::fromUtf8("Test.Frode Rønning").toUtf8() );
+
+ encoded = encodeImapFolderName( QString::fromUtf8( "Test.tom & jerry" ) );
+ QCOMPARE( encoded, QString::fromUtf8("Test.tom &- jerry") );
+ bEncoded = encodeImapFolderName( QString::fromUtf8( "Test.tom & jerry" ).toUtf8() );
+ QCOMPARE( bEncoded, QString::fromUtf8("Test.tom &- jerry").toUtf8() );
+
+ decoded = decodeImapFolderName( QString::fromUtf8( "Test.tom &- jerry" ) );
+ QCOMPARE( decoded, QString::fromUtf8("Test.tom & jerry") );
+ bDecoded = decodeImapFolderName( QString::fromUtf8( "Test.tom &- jerry" ).toUtf8() );
+ QCOMPARE( bDecoded, QString::fromUtf8("Test.tom & jerry").toUtf8() );
+
+ // Try to feed already encoded
+ encoded = encodeImapFolderName( QString::fromUtf8( "Test.Cl&AOE-udio" ) );
+ QCOMPARE( encoded, QString::fromUtf8("Test.Cl&-AOE-udio") );
+ bEncoded = encodeImapFolderName( QString::fromUtf8( "Test.Cl&AOE-udio" ).toUtf8() );
+ QCOMPARE( bEncoded, QString::fromUtf8("Test.Cl&-AOE-udio").toUtf8() );
+
+ decoded = decodeImapFolderName( QString::fromUtf8( "Test.Cl&-AOE-udio" ) );
+ QCOMPARE( decoded, QString::fromUtf8("Test.Cl&AOE-udio") );
+ bDecoded = decodeImapFolderName( QString::fromUtf8( "Test.Cl&-AOE-udio" ).toUtf8() );
+ QCOMPARE( bDecoded, QString::fromUtf8("Test.Cl&AOE-udio").toUtf8() );
+
+ // With UTF8 characters
+ bEncoded = "INBOX/&AOQ- &APY- &APw- @ &IKw-";
+ QCOMPARE( decodeImapFolderName( bEncoded ), QByteArray( "INBOX/ä ö ü @ €" ) );
+}
+
+void RFCCodecsTest::testQuotes()
+{
+ QString test("tom\"allen");
+ QCOMPARE( quoteIMAP( test ), QString("tom\\\"allen") );
+ test = "tom\'allen";
+ QCOMPARE( quoteIMAP( test ), QString("tom\'allen" ) );
+ test = "tom\\allen";
+ QCOMPARE( quoteIMAP( test ), QString("tom\\\\allen" ) );
+}
+
diff --git a/kimap/tests/testrfccodecs.h b/kimap/tests/testrfccodecs.h
new file mode 100644
index 0000000..58f3c17
--- /dev/null
+++ b/kimap/tests/testrfccodecs.h
@@ -0,0 +1,35 @@
+/*
+ This file is part of the kimap library.
+
+ Copyright (c) 2007 Allen Winter <winter@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef RFCCODECSTEST_H
+#define RFCCODECSTEST_H
+
+#include <QtCore/QObject>
+
+class RFCCodecsTest : public QObject
+{
+ Q_OBJECT
+ private Q_SLOTS:
+ void testIMAPEncoding();
+ void testQuotes();
+};
+
+#endif
diff --git a/kimap/tests/testsession.cpp b/kimap/tests/testsession.cpp
new file mode 100644
index 0000000..2535348
--- /dev/null
+++ b/kimap/tests/testsession.cpp
@@ -0,0 +1,330 @@
+/*
+ This file is part of the KDE project
+ Copyright (C) 2008 Kevin Ottens <ervin@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+ */
+
+#include <QtCore/QEventLoop>
+#include <QtCore/QObject>
+#include <QtTest/QtTest>
+
+#include "session.h"
+#include "job.h"
+#include "kimaptest/fakeserver.h"
+#include "kimaptest/mockjob.h"
+
+Q_DECLARE_METATYPE(KIMAP::Session::State)
+Q_DECLARE_METATYPE(KJob*)
+
+class SessionTest : public QObject
+{
+ Q_OBJECT
+
+ private slots:
+
+ void initTestCase()
+ {
+ qRegisterMetaType<KIMAP::Session::State>();
+ }
+
+ void shouldStartDisconnected()
+ {
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::greeting()
+ );
+ fakeServer.startAndWait();
+ KIMAP::Session s( "127.0.0.1", 5989 );
+ QSignalSpy spy(&s, SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)));
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Disconnected );
+ QTest::qWait( 600 );
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::NotAuthenticated );
+ QCOMPARE( spy.count(), 1 ); // NotAuthenticated
+ QList<QVariant> arguments = spy.takeFirst();
+ QCOMPARE( ( int )qvariant_cast<KIMAP::Session::State>(arguments.at(0)), ( int )KIMAP::Session::NotAuthenticated);
+ QCOMPARE( ( int )qvariant_cast<KIMAP::Session::State>(arguments.at(1)), ( int )KIMAP::Session::Disconnected);
+ }
+
+ void shouldFailForInvalidHosts()
+ {
+ KIMAP::Session s( "0.0.0.0", 1234 );
+ s.setTimeout(1); // 1 second timout
+
+ QSignalSpy spyFail(&s, SIGNAL(connectionFailed()));
+ QSignalSpy spyLost(&s, SIGNAL(connectionLost()));
+ QSignalSpy spyState(&s, SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)));
+
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Disconnected );
+
+ QTest::qWait( 500 );
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Disconnected );
+ QCOMPARE ( spyFail.count(), 1 );
+ QEXPECT_FAIL("", "FIXME KDE5: Don't emit connectionLost() on a failed connection", Continue);
+ QCOMPARE ( spyLost.count(), 0 );
+ QCOMPARE ( spyState.count(), 0 );
+
+ // Wait 800ms more. So now it's 1.3 seconds, check that the socket timeout has correctly been
+ // disabled, and that it hadn't fired unexpectedly.
+ QTest::qWait( 800 );
+ QCOMPARE ( spyFail.count(), 1 );
+ }
+
+ /**
+ Checks that the timeout works when the connection succeeds, but the server doesn't sends anything
+ back to the client. This could happen for example if we connected to a non-IMAP server.
+ */
+ void shouldTimeoutOnNoGreeting()
+ {
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>() );
+ fakeServer.startAndWait();
+
+ KIMAP::Session s( "127.0.0.1", 5989 );
+ s.setTimeout(2);
+ QSignalSpy spyFail(&s, SIGNAL(connectionFailed()));
+ QSignalSpy spyLost(&s, SIGNAL(connectionLost()));
+ QSignalSpy spyState(&s, SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)));
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Disconnected );
+
+ // Wait 1.8 second. Since the timeout is set to 2 seconds, the socket should be still
+ // disconnected at this point, yet the connectionFailed() signal shouldn't have been emitted.
+ QTest::qWait( 1800 );
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Disconnected );
+ QCOMPARE ( spyFail.count(), 0 );
+ QCOMPARE ( spyLost.count(), 0 );
+ QCOMPARE ( spyState.count(), 0 );
+
+ // Wait 0.5 second more. Now we are at 2.3 seconds, the socket should have timed out, and the
+ // connectionFailed() signal should have been emitted.
+ QTest::qWait( 500 );
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Disconnected );
+ QCOMPARE ( spyFail.count(), 1 );
+ QCOMPARE ( spyLost.count(), 0 );
+ QCOMPARE ( spyState.count(), 0 );
+ }
+
+ void shouldSupportPreauth()
+ {
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::preauth()
+ );
+ fakeServer.startAndWait();
+
+ KIMAP::Session s( "127.0.0.1", 5989 );
+ QSignalSpy spy(&s, SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)));
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Disconnected );
+ QTest::qWait( 500 );
+ QCOMPARE( ( int )s.state(), ( int )KIMAP::Session::Authenticated );
+ QCOMPARE( spy.count(), 1 ); // Authenticated
+ QList<QVariant> arguments = spy.takeFirst();
+ QCOMPARE( ( int )qvariant_cast<KIMAP::Session::State>(arguments.at(0)), ( int )KIMAP::Session::Authenticated);
+ QCOMPARE( ( int )qvariant_cast<KIMAP::Session::State>(arguments.at(1)), ( int )KIMAP::Session::Disconnected);
+ }
+
+ void shouldRespectStartOrder()
+ {
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::greeting()
+ );
+ fakeServer.startAndWait();
+
+ KIMAP::Session s("127.0.0.1", 5989);
+ MockJob *j1 = new MockJob(&s);
+ connect(j1, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*)));
+ MockJob *j2 = new MockJob(&s);
+ connect(j2, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*)));
+ MockJob *j3 = new MockJob(&s);
+ connect(j3, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*)));
+ MockJob *j4 = new MockJob(&s);
+ connect(j4, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*)));
+
+ j4->start();
+ j2->start();
+ j3->start();
+ j1->start();
+
+ m_expectedCalls = 4;
+ m_eventLoop.exec();
+
+ QCOMPARE(m_jobs.size(), 4);
+ QCOMPARE(m_jobs[0], j4);
+ QCOMPARE(m_jobs[1], j2);
+ QCOMPARE(m_jobs[2], j3);
+ QCOMPARE(m_jobs[3], j1);
+ }
+
+ void shouldManageQueueSize()
+ {
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::greeting()
+ );
+ fakeServer.startAndWait();
+
+ KIMAP::Session s("127.0.0.1", 5989);
+
+ QSignalSpy queueSpy(&s, SIGNAL(jobQueueSizeChanged(int)));
+
+ QCOMPARE( s.jobQueueSize(), 0 );
+
+ MockJob *j1 = new MockJob(&s);
+ MockJob *j2 = new MockJob(&s);
+ MockJob *j3 = new MockJob(&s);
+ MockJob *j4 = new MockJob(&s);
+ connect(j4, SIGNAL(result(KJob*)), &m_eventLoop, SLOT(quit()));
+
+ QCOMPARE( s.jobQueueSize(), 0 );
+
+ j1->start();
+ QCOMPARE( s.jobQueueSize(), 1 );
+ QCOMPARE( queueSpy.size(), 1 );
+ QCOMPARE( queueSpy.at( 0 ).at( 0 ).toInt(), 1 );
+
+ j2->start();
+ QCOMPARE( s.jobQueueSize(), 2 );
+ QCOMPARE( queueSpy.size(), 2 );
+ QCOMPARE( queueSpy.at( 1 ).at( 0 ).toInt(), 2 );
+
+ j3->start();
+ QCOMPARE( s.jobQueueSize(), 3 );
+ QCOMPARE( queueSpy.size(), 3 );
+ QCOMPARE( queueSpy.at( 2 ).at( 0 ).toInt(), 3 );
+
+ j4->start();
+ QCOMPARE( s.jobQueueSize(), 4 );
+ QCOMPARE( queueSpy.size(), 4 );
+ QCOMPARE( queueSpy.at( 3 ).at( 0 ).toInt(), 4 );
+
+ queueSpy.clear();
+ m_eventLoop.exec();
+
+ QCOMPARE( s.jobQueueSize(), 0 );
+
+ QCOMPARE( queueSpy.at( 0 ).at( 0 ).toInt(), 3 );
+ QCOMPARE( queueSpy.at( 1 ).at( 0 ).toInt(), 2 );
+ QCOMPARE( queueSpy.at( 2 ).at( 0 ).toInt(), 1 );
+ QCOMPARE( queueSpy.at( 3 ).at( 0 ).toInt(), 0 );
+ }
+
+ void shouldTimeoutOnNoReply()
+ {
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>()
+ << FakeServer::preauth()
+ << "C: A000001 DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ << "S: * DUMMY"
+ // We never get a OK or anything, so the job can't normally complete
+ );
+ fakeServer.startAndWait();
+
+ KIMAP::Session s( "127.0.0.1", 5989 );
+
+ QSignalSpy spyFail(&s, SIGNAL(connectionFailed()));
+ QSignalSpy spyLost(&s, SIGNAL(connectionLost()));
+ QSignalSpy spyState(&s, SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)));
+
+ MockJob *mock = new MockJob(&s);
+ mock->setCommand("DUMMY");
+
+ mock->exec();
+ // We expect to get an error here due to some timeout
+ QVERIFY( mock->error()!=0 );
+ QCOMPARE( spyFail.count(), 0 );
+ QCOMPARE( spyLost.count(), 1 );
+ QCOMPARE( spyState.count(), 2 ); // Authenticated, Disconnected
+ }
+
+ void shouldFailFirstJobOnConnectionFailed()
+ {
+ qRegisterMetaType<KJob*>();
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( QList<QByteArray>() );
+ fakeServer.startAndWait();
+
+ KIMAP::Session s( "127.0.0.1", 5989 );
+ s.setTimeout(1);
+
+ MockJob *j1 = new MockJob(&s);
+ QSignalSpy spyResult1(j1, SIGNAL(result(KJob*)));
+ QSignalSpy spyDestroyed1(j1, SIGNAL(destroyed()));
+
+ MockJob *j2 = new MockJob(&s);
+ QSignalSpy spyResult2(j2, SIGNAL(result(KJob*)));
+ QSignalSpy spyDestroyed2(j2, SIGNAL(destroyed()));
+
+ MockJob *j3 = new MockJob(&s);
+ QSignalSpy spyResult3(j3, SIGNAL(result(KJob*)));
+ QSignalSpy spyDestroyed3(j3, SIGNAL(destroyed()));
+
+ j1->start();
+ j2->start();
+ j3->start();
+
+ QCOMPARE( s.jobQueueSize(), 3);
+
+ QTest::qWait(1100);
+
+ // Check that only the first job has emitted it's result
+ QCOMPARE( spyResult1.count(), 1 );
+ QCOMPARE( spyResult2.count(), 0 );
+ QCOMPARE( spyResult3.count(), 0 );
+
+ // Check that all jobs have been deleted
+ QCOMPARE( spyDestroyed1.count(), 1 );
+ QCOMPARE( spyDestroyed2.count(), 1 );
+ QCOMPARE( spyDestroyed3.count(), 1 );
+
+ QCOMPARE( s.jobQueueSize(), 0);
+ }
+
+ public slots:
+ void jobDone(KJob *job)
+ {
+ m_jobs << job;
+
+ if (m_expectedCalls==m_jobs.size()) {
+ m_eventLoop.quit();
+ }
+ }
+
+ private:
+ QEventLoop m_eventLoop;
+ int m_expectedCalls;
+ QList<KJob*> m_jobs;
+};
+
+QTEST_MAIN(SessionTest)
+
+#include "testsession.moc"
diff --git a/kimap/tests/unsubscribejobtest.cpp b/kimap/tests/unsubscribejobtest.cpp
new file mode 100644
index 0000000..1b4afe4
--- /dev/null
+++ b/kimap/tests/unsubscribejobtest.cpp
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2009 Andras Mantia <amantia@kde.org>
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
+ Author: Kevin Ottens <kevin@kdab.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+
+#include "kimaptest/fakeserver.h"
+#include "kimap/session.h"
+#include "kimap/unsubscribejob.h"
+
+#include <QTcpSocket>
+#include <QtTest>
+#include <KDebug>
+
+class UnsubscribeJobTest: public QObject {
+ Q_OBJECT
+
+private Q_SLOTS:
+
+void testUnsubscribe_data() {
+ QTest::addColumn<QString>( "mailbox" );
+ QTest::addColumn< QList<QByteArray> >( "scenario" );
+
+ QList<QByteArray> scenario;
+ scenario << FakeServer::preauth()
+ << "C: A000001 UNSUBSCRIBE \"#news.comp.mail.mime\""
+ << "S: A000001 OK UNSUBSCRIBE completed";
+ QTest::newRow( "good" ) << "#news.comp.mail.mime" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 UNSUBSCRIBE \"INBOX-FAIL-BAD\""
+ << "S: A000001 BAD command unknown or arguments invalid";
+ QTest::newRow( "bad" ) << "INBOX-FAIL-BAD" << scenario;
+
+ scenario.clear();
+ scenario << FakeServer::preauth()
+ << "C: A000001 UNSUBSCRIBE \"INBOX-FAIL-NO\""
+ << "S: A000001 NO unsubscribe failure";
+ QTest::newRow( "no" ) << "INBOX-FAIL-NO" << scenario;
+}
+
+void testUnsubscribe()
+{
+ QFETCH( QString, mailbox );
+ QFETCH( QList<QByteArray>, scenario );
+
+ FakeServer fakeServer;
+ fakeServer.setScenario( scenario );
+ fakeServer.startAndWait();
+
+ KIMAP::Session session("127.0.0.1", 5989);
+
+ KIMAP::UnsubscribeJob *job = new KIMAP::UnsubscribeJob(&session);
+ job->setMailBox(mailbox);
+ bool result = job->exec();
+ QEXPECT_FAIL("bad" , "Expected failure on BAD scenario", Continue);
+ QEXPECT_FAIL("no" , "Expected failure on NO scenario", Continue);
+ QVERIFY(result);
+ QCOMPARE(job->mailBox(), mailbox);
+
+ fakeServer.quit();
+}
+
+
+};
+
+QTEST_KDEMAIN_CORE( UnsubscribeJobTest )
+
+#include "unsubscribejobtest.moc"
diff --git a/kimap/unsubscribejob.cpp b/kimap/unsubscribejob.cpp
new file mode 100644
index 0000000..24db31e
--- /dev/null
+++ b/kimap/unsubscribejob.cpp
@@ -0,0 +1,71 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "unsubscribejob.h"
+
+#include <KDE/KLocale>
+#include <KDE/KDebug>
+
+#include "job_p.h"
+#include "message_p.h"
+#include "session_p.h"
+#include "rfccodecs.h"
+
+namespace KIMAP
+{
+ class UnsubscribeJobPrivate : public JobPrivate
+ {
+ public:
+ UnsubscribeJobPrivate( Session *session, const QString& name ) : JobPrivate(session, name) { }
+ ~UnsubscribeJobPrivate() { }
+
+ QString mailBox;
+ };
+}
+
+using namespace KIMAP;
+
+UnsubscribeJob::UnsubscribeJob( Session *session )
+ : Job( *new UnsubscribeJobPrivate(session, i18n("Unsubscribe")) )
+{
+}
+
+UnsubscribeJob::~UnsubscribeJob()
+{
+}
+
+void UnsubscribeJob::doStart()
+{
+ Q_D(UnsubscribeJob);
+ d->tags << d->sessionInternal()->sendCommand( "UNSUBSCRIBE", '\"'+KIMAP::encodeImapFolderName( d->mailBox.toUtf8() )+'\"' );
+}
+
+void UnsubscribeJob::setMailBox( const QString &mailBox )
+{
+ Q_D(UnsubscribeJob);
+ d->mailBox = mailBox;
+}
+
+QString UnsubscribeJob::mailBox() const
+{
+ Q_D(const UnsubscribeJob);
+ return d->mailBox;
+}
+
+
diff --git a/kimap/unsubscribejob.h b/kimap/unsubscribejob.h
new file mode 100644
index 0000000..f03875e
--- /dev/null
+++ b/kimap/unsubscribejob.h
@@ -0,0 +1,52 @@
+/*
+ Copyright (c) 2009 Andras Mantia <amantia@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KIMAP_UNSUBSCRIBEJOB_H
+#define KIMAP_UNSUBSCRIBEJOB_H
+
+#include "kimap_export.h"
+
+#include "job.h"
+
+namespace KIMAP {
+
+class Session;
+class UnsubscribeJobPrivate;
+
+class KIMAP_EXPORT UnsubscribeJob : public Job
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(UnsubscribeJob)
+
+ friend class SessionPrivate;
+
+ public:
+ explicit UnsubscribeJob( Session *session );
+ virtual ~UnsubscribeJob();
+
+ void setMailBox( const QString &mailBox );
+ QString mailBox() const;
+
+ protected:
+ virtual void doStart();
+};
+
+}
+
+#endif