[kde-doc-english] [kmailtransport] /: Move smtp kioslave here

Montel Laurent montel at kde.org
Sun Apr 3 09:18:54 UTC 2016


Git commit 41707df9615e92dc8312f6259251e2dedd1bfdd8 by Montel Laurent.
Committed on 03/04/2016 at 09:18.
Pushed by mlaurent into branch 'master'.

Move smtp kioslave here

M  +3    -3    CMakeLists.txt
A  +1    -0    kioslave/.krazy
A  +3    -0    kioslave/CMakeLists.txt
A  +22   -0    kioslave/cmake/COPYING-CMAKE-SCRIPTS
A  +53   -0    kioslave/cmake/FindSasl2.cmake
A  +1    -0    kioslave/docs/CMakeLists.txt
A  +3    -0    kioslave/docs/smtp/CMakeLists.txt
A  +23   -0    kioslave/docs/smtp/index.docbook
A  +3    -0    kioslave/src/CMakeLists.txt
A  +50   -0    kioslave/src/common.h     [License: LGPL (v2+)]
A  +44   -0    kioslave/src/smtp/CMakeLists.txt
A  +2    -0    kioslave/src/smtp/Messages.sh
A  +11   -0    kioslave/src/smtp/TODO
A  +124  -0    kioslave/src/smtp/capabilities.cpp     [License: GPL (v2) (+Qt exception)]
A  +87   -0    kioslave/src/smtp/capabilities.h     [License: GPL (v2) (+Qt exception)]
A  +653  -0    kioslave/src/smtp/command.cpp     [License: GPL (v2) (+Qt exception)]
A  +331  -0    kioslave/src/smtp/command.h     [License: GPL (v2) (+Qt exception)]
A  +33   -0    kioslave/src/smtp/compliance.txt
A  +88   -0    kioslave/src/smtp/kioslavesession.cpp     [License: LGPL (v2+)]
A  +52   -0    kioslave/src/smtp/kioslavesession.h     [License: LGPL (v2+)]
A  +196  -0    kioslave/src/smtp/request.cpp     [License: GPL (v2) (+Qt exception)]
A  +180  -0    kioslave/src/smtp/request.h     [License: GPL (v2) (+Qt exception)]
A  +169  -0    kioslave/src/smtp/response.cpp     [License: GPL (v2) (+Qt exception)]
A  +173  -0    kioslave/src/smtp/response.h     [License: GPL (v2) (+Qt exception)]
A  +654  -0    kioslave/src/smtp/smtp.cpp     [License: BSD]
A  +126  -0    kioslave/src/smtp/smtp.h     [License: BSD]
A  +16   -0    kioslave/src/smtp/smtp.protocol
A  +16   -0    kioslave/src/smtp/smtps.protocol
A  +61   -0    kioslave/src/smtp/smtpsessioninterface.cpp     [License: LGPL (v2+)]
A  +96   -0    kioslave/src/smtp/smtpsessioninterface.h     [License: LGPL (v2+)]
A  +67   -0    kioslave/src/smtp/tests/CMakeLists.txt
A  +120  -0    kioslave/src/smtp/tests/fakesession.h     [License: LGPL (v2+)]
A  +206  -0    kioslave/src/smtp/tests/interactivesmtpserver.cpp     [License: GPL (v2) (+Qt exception)]
A  +81   -0    kioslave/src/smtp/tests/interactivesmtpserver.h     [License: GPL (v2) (+Qt exception)]
A  +83   -0    kioslave/src/smtp/tests/requesttest.cpp     [License: GPL (v2)]
A  +35   -0    kioslave/src/smtp/tests/requesttest.h     [License: GPL (v2)]
A  +26   -0    kioslave/src/smtp/tests/test_capabilities.cpp     [License: UNKNOWN]  *
A  +677  -0    kioslave/src/smtp/tests/test_commands.cpp     [License: UNKNOWN]  *
A  +90   -0    kioslave/src/smtp/tests/test_headergeneration.cpp     [License: UNKNOWN]  *
A  +106  -0    kioslave/src/smtp/tests/test_responseparser.cpp     [License: UNKNOWN]  *
A  +32   -0    kioslave/src/smtp/tests/test_responseparser.h     [License: LGPL (v2+)]
A  +127  -0    kioslave/src/smtp/transactionstate.cpp     [License: GPL (v2) (+Qt exception)]
A  +230  -0    kioslave/src/smtp/transactionstate.h     [License: GPL (v2) (+Qt exception)]
A  +3    -0    kmailtransport.categories

The files marked with a * at the end have a non valid license. Please read: http://techbase.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.


http://commits.kde.org/kmailtransport/41707df9615e92dc8312f6259251e2dedd1bfdd8

diff --git a/CMakeLists.txt b/CMakeLists.txt
index cc9a6cb..61c51ef 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,7 +38,7 @@ ecm_setup_version(${KMAILTRANSPORT_LIB_VERSION} VARIABLE_PREFIX MAILTRANSPORT
 find_package(KF5KCMUtils ${KF5_VERSION} CONFIG REQUIRED)
 find_package(KF5ConfigWidgets ${KF5_VERSION} CONFIG REQUIRED)
 find_package(KF5Wallet ${KF5_VERSION} CONFIG REQUIRED)
-
+find_package(KF5DocTools ${KF5_VERSION} CONFIG REQUIRED)
 find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED)
 find_package(KF5Akonadi ${AKONADI_LIB_VERSION} CONFIG REQUIRED)
 find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED)
@@ -77,12 +77,12 @@ install(EXPORT KF5MailTransportTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
 ########### Targets ###########
 add_subdirectory(cmake)
 add_subdirectory(src)
-
+add_subdirectory(kioslave)
 if(BUILD_TESTING)
     add_subdirectory(tests)
     add_subdirectory(autotests)
 endif()
 
-
+install( FILES kmailtransport.categories DESTINATION ${KDE_INSTALL_CONFDIR} )
 feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
 
diff --git a/kioslave/.krazy b/kioslave/.krazy
new file mode 100644
index 0000000..0b16e7f
--- /dev/null
+++ b/kioslave/.krazy
@@ -0,0 +1 @@
+SKIP /tests/
diff --git a/kioslave/CMakeLists.txt b/kioslave/CMakeLists.txt
new file mode 100644
index 0000000..9246c69
--- /dev/null
+++ b/kioslave/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_definitions("-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII")
+add_subdirectory(src)
+add_subdirectory(docs)
diff --git a/kioslave/cmake/COPYING-CMAKE-SCRIPTS b/kioslave/cmake/COPYING-CMAKE-SCRIPTS
new file mode 100644
index 0000000..4b41776
--- /dev/null
+++ b/kioslave/cmake/COPYING-CMAKE-SCRIPTS
@@ -0,0 +1,22 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products 
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/kioslave/cmake/FindSasl2.cmake b/kioslave/cmake/FindSasl2.cmake
new file mode 100644
index 0000000..ec3bb81
--- /dev/null
+++ b/kioslave/cmake/FindSasl2.cmake
@@ -0,0 +1,53 @@
+#
+# - Try to find the sasl2 directory library
+# Once done this will define
+#
+#  Sasl2_FOUND - system has SASL2
+#  Sasl2_INCLUDE_DIRS - the SASL2 include directory
+#  Sasl2_LIBRARIES - The libraries needed to use SASL2
+
+# Copyright (c) 2006, 2007 Laurent Montel, <montel at kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+# Note: libsasl2.pc doesn't export the include dir.
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_Sasl2 libsasl2)
+
+find_path(Sasl2_INCLUDE_DIRS sasl/sasl.h
+)
+
+# libsasl2 add for windows, because the windows package of cyrus-sasl2
+# contains a libsasl2 also for msvc which is not standard conform
+find_library(Sasl2_LIBRARIES
+  NAMES sasl2 libsasl2
+  HINTS ${PC_Sasl2_LIBRARY_DIRS}
+)
+
+set(Sasl2_VERSION ${PC_Sasl2_VERSION})
+
+if(NOT Sasl2_VERSION)
+  if(EXISTS ${Sasl2_INCLUDE_DIRS}/sasl/sasl.h)
+    file(READ ${Sasl2_INCLUDE_DIRS}/sasl/sasl.h SASL2_H_CONTENT)
+    string(REGEX MATCH "#define SASL_VERSION_MAJOR[ ]+[0-9]+" SASL2_VERSION_MAJOR_MATCH ${SASL2_H_CONTENT})
+    string(REGEX MATCH "#define SASL_VERSION_MINOR[ ]+[0-9]+" SASL2_VERSION_MINOR_MATCH ${SASL2_H_CONTENT})
+    string(REGEX MATCH "#define SASL_VERSION_STEP[ ]+[0-9]+" SASL2_VERSION_STEP_MATCH ${SASL2_H_CONTENT})
+
+    string(REGEX REPLACE ".*_MAJOR[ ]+(.*)" "\\1" SASL2_VERSION_MAJOR ${SASL2_VERSION_MAJOR_MATCH})
+    string(REGEX REPLACE ".*_MINOR[ ]+(.*)" "\\1" SASL2_VERSION_MINOR ${SASL2_VERSION_MINOR_MATCH})
+    string(REGEX REPLACE ".*_STEP[ ]+(.*)" "\\1"  SASL2_VERSION_STEP  ${SASL2_VERSION_STEP_MATCH})
+
+    set(Sasl2_VERSION "${SASL2_VERSION_MAJOR}.${SASL2_VERSION_MINOR}.${SASL2_VERSION_STEP}")
+  endif()
+endif()
+
+include(FindPackageHandleStandardArgs)
+
+find_package_handle_standard_args(Sasl2
+    FOUND_VAR Sasl2_FOUND
+    REQUIRED_VARS Sasl2_LIBRARIES Sasl2_INCLUDE_DIRS
+    VERSION_VAR Sasl2_VERSION
+)
+
+mark_as_advanced(Sasl2_LIBRARIES Sasl2_INCLUDE_DIRS Sasl2_VERSION)
diff --git a/kioslave/docs/CMakeLists.txt b/kioslave/docs/CMakeLists.txt
new file mode 100644
index 0000000..1751e50
--- /dev/null
+++ b/kioslave/docs/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(smtp)
diff --git a/kioslave/docs/smtp/CMakeLists.txt b/kioslave/docs/smtp/CMakeLists.txt
new file mode 100644
index 0000000..11d31fa
--- /dev/null
+++ b/kioslave/docs/smtp/CMakeLists.txt
@@ -0,0 +1,3 @@
+########### install files ###############
+kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kioslave5/smtp)
+
diff --git a/kioslave/docs/smtp/index.docbook b/kioslave/docs/smtp/index.docbook
new file mode 100644
index 0000000..1939851
--- /dev/null
+++ b/kioslave/docs/smtp/index.docbook
@@ -0,0 +1,23 @@
+<?xml version="1.0" ?>
+<!DOCTYPE article PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN"
+"dtd/kdedbx45.dtd" [
+<!ENTITY % addindex "IGNORE">
+<!ENTITY % English "INCLUDE" > <!-- change language only here -->
+]>
+	
+<article lang="&language;" id="smtp">
+<title>smtp</title>
+<articleinfo>
+<authorgroup>
+<author>&Ferdinand.Gassauer; &Ferdinand.Gassauer.mail;</author>
+<!-- TRANS:ROLES_OF_TRANSLATORS -->
+</authorgroup>
+</articleinfo>
+<para>
+A protocol to send mail from the client workstation to the mail server.
+</para>
+
+<para> See : <ulink url="http://cr.yp.to/smtp.html">Simple Mail Transfer Protocol </ulink>.
+</para>
+
+</article>
diff --git a/kioslave/src/CMakeLists.txt b/kioslave/src/CMakeLists.txt
new file mode 100644
index 0000000..275c3d1
--- /dev/null
+++ b/kioslave/src/CMakeLists.txt
@@ -0,0 +1,3 @@
+remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY)
+add_subdirectory(smtp)
+
diff --git a/kioslave/src/common.h b/kioslave/src/common.h
new file mode 100644
index 0000000..acdeb17
--- /dev/null
+++ b/kioslave/src/common.h
@@ -0,0 +1,50 @@
+/*  This file is part of the KDE project
+    Copyright (C) 2008 Jarosław Staniek <staniek at 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 _KIOSLAVE_COMMON_H
+#define _KIOSLAVE_COMMON_H
+
+#include <stdio.h>
+#include <QFile>
+#include <QDir>
+
+extern "C" {
+#include <sasl/sasl.h>
+}
+
+inline bool initSASL()
+{
+#ifdef Q_OS_WIN32  //krazy:exclude=cpp
+    QByteArray libInstallPath(QFile::encodeName(QDir::toNativeSeparators(KGlobal::dirs()->installPath("lib") + QLatin1String("sasl2"))));
+    QByteArray configPath(QFile::encodeName(QDir::toNativeSeparators(KGlobal::dirs()->installPath("config") + QLatin1String("sasl2"))));
+    if (sasl_set_path(SASL_PATH_TYPE_PLUGIN, libInstallPath.data()) != SASL_OK ||
+            sasl_set_path(SASL_PATH_TYPE_CONFIG, configPath.data()) != SASL_OK) {
+        fprintf(stderr, "SASL path initialization failed!\n");
+        return false;
+    }
+#endif
+
+    if (sasl_client_init(NULL) != SASL_OK) {
+        fprintf(stderr, "SASL library initialization failed!\n");
+        return false;
+    }
+    return true;
+}
+
+#endif
diff --git a/kioslave/src/smtp/CMakeLists.txt b/kioslave/src/smtp/CMakeLists.txt
new file mode 100644
index 0000000..3f809a2
--- /dev/null
+++ b/kioslave/src/smtp/CMakeLists.txt
@@ -0,0 +1,44 @@
+
+
+if (BUILD_TESTING)
+add_subdirectory(tests)
+endif()	
+set(smtp_optional_includes)
+set(smtp_optional_libs)
+
+if (Sasl2_FOUND)
+   set(smtp_optional_includes ${smtp_optional_includes} ${Sasl2_INCLUDE_DIRS})
+   set(smtp_optional_libs     ${smtp_optional_libs}     ${Sasl2_LIBRARIES})
+endif()
+
+
+include_directories(  ${smtp_optional_includes}  )
+
+
+########### next target ###############
+
+set(kio_smtp_PART_SRCS
+   smtp.cpp
+   request.cpp
+   response.cpp
+   capabilities.cpp
+   command.cpp
+   transactionstate.cpp
+   smtpsessioninterface.cpp
+   kioslavesession.cpp
+)
+
+ecm_qt_declare_logging_category(kio_smtp_PART_SRCS HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME log_smtp)
+
+add_library(kio_smtp MODULE ${kio_smtp_PART_SRCS})
+
+
+target_link_libraries(kio_smtp  KF5::KIOCore KF5::I18n Qt5::Network ${smtp_optional_libs})
+set_target_properties(kio_smtp PROPERTIES OUTPUT_NAME "smtp")
+
+install(TARGETS kio_smtp  DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio/ )
+
+########### install files ###############
+
+install( FILES smtp.protocol smtps.protocol  DESTINATION  ${KDE_INSTALL_KSERVICES5DIR} )
+
diff --git a/kioslave/src/smtp/Messages.sh b/kioslave/src/smtp/Messages.sh
new file mode 100644
index 0000000..c0be647
--- /dev/null
+++ b/kioslave/src/smtp/Messages.sh
@@ -0,0 +1,2 @@
+#! /usr/bin/env bash
+$XGETTEXT *.cpp -o $podir/kio_smtp.pot
diff --git a/kioslave/src/smtp/TODO b/kioslave/src/smtp/TODO
new file mode 100644
index 0000000..cad79f1
--- /dev/null
+++ b/kioslave/src/smtp/TODO
@@ -0,0 +1,11 @@
+1. Double check the error handling and review error message in various
+   failure modes.
+2. Implement the CHUNKING extension (rfc 3030; as soon as I find an
+   SMTP server that supports it).
+3. Better error message (translated standard meanings of the known
+   response codes, ENHANCEDSTATUSCODES extension (rfc2034)).
+4. (KIO) MultiPutJob to support pipelining across messages.
+5. Ged rid of slave's header generation after checking who on earth
+   uses that...
+
+and further refactoring to make the code pleasant to look at ;-)
diff --git a/kioslave/src/smtp/capabilities.cpp b/kioslave/src/smtp/capabilities.cpp
new file mode 100644
index 0000000..061f740
--- /dev/null
+++ b/kioslave/src/smtp/capabilities.cpp
@@ -0,0 +1,124 @@
+/*  -*- c++ -*-
+    capabilities.cc
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#include "capabilities.h"
+#include "response.h"
+
+namespace KioSMTP
+{
+
+Capabilities Capabilities::fromResponse(const Response &ehlo)
+{
+    Capabilities c;
+
+    // first, check whether the response was valid and indicates success:
+    if (!ehlo.isOk()
+            || ehlo.code() / 10 != 25 // ### restrict to 250 only?
+            || ehlo.lines().empty()) {
+        return c;
+    }
+
+    QCStringList l = ehlo.lines();
+
+    for (QCStringList::const_iterator it = ++l.constBegin(); it != l.constEnd(); ++it) {
+        c.add(QString::fromLatin1(*it));
+    }
+
+    return c;
+}
+
+void Capabilities::add(const QString &cap, bool replace)
+{
+    QStringList tokens = cap.toUpper().split(QLatin1Char(' '));
+    if (tokens.empty()) {
+        return;
+    }
+    QString name = tokens.front();
+    tokens.pop_front();
+    add(name, tokens, replace);
+}
+
+void Capabilities::add(const QString &name, const QStringList &args, bool replace)
+{
+    if (replace) {
+        mCapabilities[name] = args;
+    } else {
+        mCapabilities[name] += args;
+    }
+}
+
+QString Capabilities::createSpecialResponse(bool tls) const
+{
+    QStringList result;
+    if (tls) {
+        result.push_back(QStringLiteral("STARTTLS"));
+    }
+    result += saslMethodsQSL();
+    if (have("PIPELINING")) {
+        result.push_back(QStringLiteral("PIPELINING"));
+    }
+    if (have("8BITMIME")) {
+        result.push_back(QStringLiteral("8BITMIME"));
+    }
+    if (have("SIZE")) {
+        bool ok = false;
+        unsigned int size = 0;
+        if (!mCapabilities[QStringLiteral("SIZE")].isEmpty()) {
+            size = mCapabilities[QStringLiteral("SIZE")].front().toUInt(&ok);
+        }
+        if (ok && !size) {
+            result.push_back(QStringLiteral("SIZE=*"));    // any size
+        } else if (ok) {
+            result.push_back(QStringLiteral("SIZE=%1").arg(size));    // fixed max
+        } else {
+            result.push_back(QStringLiteral("SIZE"));    // indetermined
+        }
+    }
+    return result.join(QStringLiteral(" "));
+}
+
+QStringList Capabilities::saslMethodsQSL() const
+{
+    QStringList result;
+    for (QMap<QString, QStringList>::const_iterator it = mCapabilities.begin();
+            it != mCapabilities.end(); ++it) {
+        if (it.key() == QLatin1String("AUTH")) {
+            result += it.value();
+        } else if (it.key().startsWith(QLatin1String("AUTH="))) {
+            result.push_back(it.key().mid(qstrlen("AUTH=")));
+            result += it.value();
+        }
+    }
+    result.removeDuplicates();
+    return result;
+}
+
+} // namespace KioSMTP
diff --git a/kioslave/src/smtp/capabilities.h b/kioslave/src/smtp/capabilities.h
new file mode 100644
index 0000000..b501c95
--- /dev/null
+++ b/kioslave/src/smtp/capabilities.h
@@ -0,0 +1,87 @@
+/*  -*- c++ -*-
+    capabilities.h
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#ifndef __KIOSMTP_CAPABILITIES_H__
+#define __KIOSMTP_CAPABILITIES_H__
+
+#include <QMap>
+
+#include <QStringList>
+
+namespace KioSMTP
+{
+
+class Response;
+
+class Capabilities
+{
+public:
+    Capabilities()
+    {
+    }
+
+    static Capabilities fromResponse(const Response &response);
+
+    void add(const QString &cap, bool replace = false);
+    void add(const QString &name, const QStringList &args, bool replace = false);
+    void clear()
+    {
+        mCapabilities.clear();
+    }
+
+    bool have(const QString &cap) const
+    {
+        return mCapabilities.find(cap.toUpper()) != mCapabilities.end();
+    }
+    bool have(const QByteArray &cap) const
+    {
+        return have(QString::fromLatin1(cap));
+    }
+    bool have(const char *cap) const
+    {
+        return have(QString::fromLatin1(cap));
+    }
+
+    QString asMetaDataString() const;
+
+    QString authMethodMetaData() const;
+
+    QString createSpecialResponse(bool tls) const;
+
+    QStringList saslMethodsQSL() const;
+private:
+
+    QMap<QString, QStringList> mCapabilities;
+};
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_CAPABILITIES_H__
diff --git a/kioslave/src/smtp/command.cpp b/kioslave/src/smtp/command.cpp
new file mode 100644
index 0000000..410f3b3
--- /dev/null
+++ b/kioslave/src/smtp/command.cpp
@@ -0,0 +1,653 @@
+/*  -*- c++ -*-
+    command.cc
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#include "command.h"
+#include "smtp_debug.h"
+
+#include "smtpsessioninterface.h"
+#include "response.h"
+#include "transactionstate.h"
+
+#include <KLocalizedString>
+#include <kio/slavebase.h> // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase
+
+#include <QtCore/QUrl>
+
+#include <assert.h>
+
+namespace KioSMTP
+{
+
+static const 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 }
+};
+
+//
+// Command (base class)
+//
+
+Command::Command(SMTPSessionInterface *smtp, int flags)
+    : mSMTP(smtp)
+    , mComplete(false)
+    , mNeedResponse(false)
+    , mFlags(flags)
+{
+    assert(smtp);
+}
+
+Command::~Command()
+{
+}
+
+bool Command::processResponse(const Response &r, TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mComplete = true;
+    mNeedResponse = false;
+    return r.isOk();
+}
+
+void Command::ungetCommandLine(const QByteArray &cmdLine, TransactionState *ts)
+{
+    Q_UNUSED(cmdLine)
+    Q_UNUSED(ts)
+    mComplete = false;
+}
+
+Command *Command::createSimpleCommand(int which, SMTPSessionInterface *smtp)
+{
+    switch (which) {
+    case STARTTLS:
+        return new StartTLSCommand(smtp);
+    case DATA:
+        return new DataCommand(smtp);
+    case NOOP:
+        return new NoopCommand(smtp);
+    case RSET:
+        return new RsetCommand(smtp);
+    case QUIT:
+        return new QuitCommand(smtp);
+    default:
+        return 0;
+    }
+}
+
+//
+// relay methods:
+//
+
+void Command::parseFeatures(const Response &r)
+{
+    mSMTP->parseFeatures(r);
+}
+
+int Command::startSsl()
+{
+    return mSMTP->startSsl();
+}
+
+bool Command::haveCapability(const char *cap) const
+{
+    return mSMTP->haveCapability(cap);
+}
+
+//
+// EHLO / HELO
+//
+
+QByteArray EHLOCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mNeedResponse = true;
+    mComplete = mEHLONotSupported;
+    const char *cmd = mEHLONotSupported ? "HELO " : "EHLO ";
+    return cmd + QUrl::toAce(mHostname) + "\r\n";   //krazy:exclude=qclasses
+}
+
+bool EHLOCommand::processResponse(const Response &r, TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mNeedResponse = false;
+    // "command not {recognized,implemented}" response:
+    if (r.code() == 500 || r.code() == 502) {
+        if (mEHLONotSupported) {   // HELO failed...
+            mSMTP->error(KIO::ERR_INTERNAL_SERVER,
+                         i18n("The server rejected both EHLO and HELO commands "
+                              "as unknown or unimplemented.\n"
+                              "Please contact the server's system administrator."));
+            return false;
+        }
+        mEHLONotSupported = true; // EHLO failed, but that's ok.
+        return true;
+    }
+    mComplete = true;
+    if (r.code() / 10 ==  25) {   // 25x: success
+        parseFeatures(r);
+        return true;
+    }
+    mSMTP->error(KIO::ERR_UNKNOWN,
+                 i18n("Unexpected server response to %1 command.\n%2",
+                      (mEHLONotSupported ? QStringLiteral("HELO") : QStringLiteral("EHLO")),
+                      r.errorMessage()));
+    return false;
+}
+
+//
+// STARTTLS - rfc 3207
+//
+
+QByteArray StartTLSCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mComplete = true;
+    mNeedResponse = true;
+    return "STARTTLS\r\n";
+}
+
+bool StartTLSCommand::processResponse(const Response &r, TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mNeedResponse = false;
+    if (r.code() != 220) {
+        mSMTP->error(r.errorCode(),
+                     i18n("Your SMTP server does not support TLS. "
+                          "Disable TLS, if you want to connect "
+                          "without encryption."));
+        return false;
+    }
+
+    if (startSsl()) {
+        return true;
+    } else {
+        //qCDebug(SMTP_LOG) << "TLS negotiation failed!";
+        mSMTP->informationMessageBox(
+            i18n("Your SMTP server claims to "
+                 "support TLS, but negotiation "
+                 "was unsuccessful.\nYou can "
+                 "disable TLS in the SMTP account settings dialog."),
+            i18n("Connection Failed"));
+        return false;
+    }
+}
+
+#define SASLERROR mSMTP->error(KIO::ERR_COULD_NOT_AUTHENTICATE, \
+                               i18n("An error occurred during authentication: %1",  \
+                                    QString::fromUtf8( sasl_errdetail( conn ) )));
+
+//
+// AUTH - rfc 2554
+//
+AuthCommand::AuthCommand(SMTPSessionInterface *smtp,
+                         const char *mechanisms,
+                         const QString &aFQDN,
+                         KIO::AuthInfo &ai)
+    : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline)
+    , mAi(&ai)
+    , mFirstTime(true)
+{
+    mMechusing = 0;
+    int result;
+    conn = 0;
+    client_interact = 0;
+    mOut = 0;
+    mOutlen = 0;
+    mOneStep = false;
+
+    const QByteArray ba = aFQDN.toLatin1();
+    result = sasl_client_new("smtp", ba.constData(),
+                             0, 0, callbacks, 0, &conn);
+    if (result != SASL_OK) {
+        SASLERROR
+        return;
+    }
+    do {
+        result = sasl_client_start(conn, mechanisms,
+                                   &client_interact, &mOut, &mOutlen, &mMechusing);
+
+        if (result == SASL_INTERACT) {
+            if (!saslInteract(client_interact)) {
+                return;
+            };
+        }
+    } while (result == SASL_INTERACT);
+    if (result != SASL_CONTINUE && result != SASL_OK) {
+        SASLERROR
+        return;
+    }
+    if (result == SASL_OK) {
+        mOneStep = true;
+    }
+    qCDebug(SMTP_LOG) << "Mechanism: " << mMechusing << " one step: " << mOneStep;
+}
+
+AuthCommand::~AuthCommand()
+{
+    if (conn) {
+        qCDebug(SMTP_LOG) << "dispose sasl connection";
+        sasl_dispose(&conn);
+        conn = 0;
+    }
+}
+
+bool AuthCommand::saslInteract(void *in)
+{
+    qCDebug(SMTP_LOG) << "saslInteract: ";
+    sasl_interact_t *interact = (sasl_interact_t *) in;
+
+    //some mechanisms do not require username && pass, so don'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) {
+
+            if (mAi->username.isEmpty() || mAi->password.isEmpty()) {
+                if (!mSMTP->openPasswordDialog(*mAi)) {
+                    mSMTP->error(KIO::ERR_ABORTED, i18n("No authentication details supplied."));
+                    return false;
+                }
+            }
+            break;
+        }
+    }
+
+    interact = (sasl_interact_t *) in;
+    while (interact->id != SASL_CB_LIST_END) {
+        switch (interact->id) {
+        case SASL_CB_USER:
+        case SASL_CB_AUTHNAME: {
+            qCDebug(SMTP_LOG) << "SASL_CB_[USER|AUTHNAME]: " << mAi->username;
+            const QByteArray baUserName = mAi->username.toUtf8();
+            interact->result = strdup(baUserName.constData());
+            interact->len = strlen((const char *) interact->result);
+            break;
+        }
+        case SASL_CB_PASS: {
+            qCDebug(SMTP_LOG) << "SASL_CB_PASS: [HIDDEN]";
+            const QByteArray baPassword = mAi->password.toUtf8();
+            interact->result = strdup(baPassword.constData());
+            interact->len = strlen((const char *) interact->result);
+            break;
+        }
+        default:
+            interact->result = NULL;
+            interact->len = 0;
+            break;
+        }
+        interact++;
+    }
+    return true;
+}
+
+bool AuthCommand::doNotExecute(const TransactionState *ts) const
+{
+    Q_UNUSED(ts)
+    return !mMechusing;
+}
+
+void AuthCommand::ungetCommandLine(const QByteArray &s, TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mUngetSASLResponse = s;
+    mComplete = false;
+}
+
+QByteArray AuthCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mNeedResponse = true;
+    QByteArray cmd;
+
+    QByteArray challenge;
+    if (!mUngetSASLResponse.isNull()) {
+        // implement un-ungetCommandLine
+        cmd = mUngetSASLResponse;
+        mUngetSASLResponse = 0;
+    } else if (mFirstTime) {
+        QString firstCommand = QLatin1String("AUTH ") + QString::fromLatin1(mMechusing);
+
+        challenge = QByteArray::fromRawData(mOut, mOutlen).toBase64();
+        if (!challenge.isEmpty()) {
+            firstCommand += QLatin1Char(' ');
+            firstCommand += QString::fromLatin1(challenge.data(), challenge.size());
+        }
+        cmd = firstCommand.toLatin1();
+
+        if (mOneStep) {
+            mComplete = true;
+        }
+    } else {
+//      qCDebug(SMTP_LOG) << "SS: '" << mLastChallenge << "'";
+        challenge = QByteArray::fromBase64(mLastChallenge);
+        int result;
+        do {
+            result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
+                                      challenge.size(),
+                                      &client_interact,
+                                      &mOut, &mOutlen);
+            if (result == SASL_INTERACT) {
+                if (!saslInteract(client_interact)) {
+                    return "";
+                };
+            }
+        } while (result == SASL_INTERACT);
+        if (result != SASL_CONTINUE && result != SASL_OK) {
+            qCDebug(SMTP_LOG) << "sasl_client_step failed with: " << result;
+            SASLERROR
+            return "";
+        }
+        cmd = QByteArray::fromRawData(mOut, mOutlen).toBase64();
+
+//      qCDebug(SMTP_LOG) << "CC: '" << cmd << "'";
+        mComplete = (result == SASL_OK);
+    }
+    cmd += "\r\n";
+    return cmd;
+}
+
+bool AuthCommand::processResponse(const Response &r, TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    if (!r.isOk()) {
+        if (mFirstTime) {
+            if (haveCapability("AUTH")) {
+                QString chooseADifferentMsg(i18n("Choose a different authentication method."));
+                mSMTP->error(KIO::ERR_COULD_NOT_LOGIN,
+                             (mMechusing ? i18n("Your SMTP server does not support %1.", QString::fromLatin1(mMechusing))
+                              : i18n("Your SMTP server does not support (unspecified method)."))
+                             + QLatin1Char('\n') + chooseADifferentMsg + QLatin1Char('\n') + r.errorMessage());
+            } else {
+                mSMTP->error(KIO::ERR_COULD_NOT_LOGIN,
+                             i18n("Your SMTP server does not support authentication.\n"
+                                  "%1", r.errorMessage()));
+            }
+        } else {
+            mSMTP->error(KIO::ERR_COULD_NOT_LOGIN,
+                         i18n("Authentication failed.\n"
+                              "Most likely the password is wrong.\n"
+                              "%1", r.errorMessage()));
+        }
+        return false;
+    }
+    mFirstTime = false;
+    mLastChallenge = r.lines().at(0); // ### better join all lines with \n?
+    mNeedResponse = false;
+    return true;
+}
+
+//
+// MAIL FROM:
+//
+
+QByteArray MailFromCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mComplete = true;
+    mNeedResponse = true;
+    QByteArray cmdLine = "MAIL FROM:<" + mAddr + '>';
+    if (m8Bit && haveCapability("8BITMIME")) {
+        cmdLine += " BODY=8BITMIME";
+    }
+    if (mSize && haveCapability("SIZE")) {
+        cmdLine += " SIZE=" + QByteArray().setNum(mSize);
+    }
+    return cmdLine + "\r\n";
+}
+
+bool MailFromCommand::processResponse(const Response &r, TransactionState *ts)
+{
+    assert(ts);
+    mNeedResponse = false;
+
+    if (r.code() == 250) {
+        return true;
+    }
+
+    ts->setMailFromFailed(QString::fromLatin1(mAddr), r);
+    return false;
+}
+
+//
+// RCPT TO:
+//
+
+QByteArray RcptToCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mComplete = true;
+    mNeedResponse = true;
+    return "RCPT TO:<" + mAddr + ">\r\n";
+}
+
+bool RcptToCommand::processResponse(const Response &r, TransactionState *ts)
+{
+    assert(ts);
+    mNeedResponse = false;
+
+    if (r.code() == 250) {
+        ts->setRecipientAccepted();
+        return true;
+    }
+
+    ts->addRejectedRecipient(QString::fromLatin1(mAddr), r.errorMessage());
+    return false;
+}
+
+//
+// DATA (only initial processing!)
+//
+
+QByteArray DataCommand::nextCommandLine(TransactionState *ts)
+{
+    assert(ts);
+    mComplete = true;
+    mNeedResponse = true;
+    ts->setDataCommandIssued(true);
+    return "DATA\r\n";
+}
+
+void DataCommand::ungetCommandLine(const QByteArray &cmd, TransactionState *ts)
+{
+    Q_UNUSED(cmd)
+    assert(ts);
+    mComplete = false;
+    ts->setDataCommandIssued(false);
+}
+
+bool DataCommand::processResponse(const Response &r, TransactionState *ts)
+{
+    assert(ts);
+    mNeedResponse = false;
+
+    if (r.code() == 354) {
+        ts->setDataCommandSucceeded(true, r);
+        return true;
+    }
+
+    ts->setDataCommandSucceeded(false, r);
+    return false;
+}
+
+//
+// DATA (data transfer)
+//
+void TransferCommand::ungetCommandLine(const QByteArray &cmd, TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    if (cmd.isEmpty()) {
+        return;    // don't change state when we can't detect the unget in
+    }
+    // the next nextCommandLine !!
+    mWasComplete = mComplete;
+    mComplete = false;
+    mNeedResponse = false;
+    mUngetBuffer = cmd;
+}
+
+bool TransferCommand::doNotExecute(const TransactionState *ts) const
+{
+    assert(ts);
+    return ts->failed();
+}
+
+QByteArray TransferCommand::nextCommandLine(TransactionState *ts)
+{
+    assert(ts);   // let's rely on it ( at least for the moment )
+    assert(!isComplete());
+    assert(!ts->failed());
+
+    static const QByteArray dotCRLF = ".\r\n";
+    static const QByteArray CRLFdotCRLF = "\r\n.\r\n";
+
+    if (!mUngetBuffer.isEmpty()) {
+        const QByteArray ret = mUngetBuffer;
+        mUngetBuffer = 0;
+        if (mWasComplete) {
+            mComplete = true;
+            mNeedResponse = true;
+        }
+        return ret; // don't prepare(), it's slave-generated or already prepare()d
+    }
+
+    // normal processing:
+
+    qCDebug(SMTP_LOG) << "requesting data";
+    mSMTP->dataReq();
+    QByteArray ba;
+    int result = mSMTP->readData(ba);
+    qCDebug(SMTP_LOG) << "got " << result << " bytes";
+    if (result > 0) {
+        return prepare(ba);
+    } else if (result < 0) {
+        ts->setFailedFatally(KIO::ERR_INTERNAL,
+                             i18n("Could not read data from application."));
+        mComplete = true;
+        mNeedResponse = true;
+        return 0;
+    }
+    mComplete = true;
+    mNeedResponse = true;
+    return mLastChar == '\n' ? dotCRLF : CRLFdotCRLF;
+}
+
+bool TransferCommand::processResponse(const Response &r, TransactionState *ts)
+{
+    mNeedResponse = false;
+    assert(ts);
+    ts->setComplete();
+    if (!r.isOk()) {
+        ts->setFailed();
+        mSMTP->error(r.errorCode(),
+                     i18n("The message content was not accepted.\n"
+                          "%1", r.errorMessage()));
+        return false;
+    }
+    return true;
+}
+
+static QByteArray dotstuff_lf2crlf(const QByteArray &ba, char &last)
+{
+    QByteArray result(ba.size() * 2 + 1, 0);   // worst case: repeated "[.]\n"
+    const char *s = ba.data();
+    const char *const send = ba.data() + ba.size();
+    char *d = result.data();
+
+    while (s < send) {
+        const char ch = *s++;
+        if (ch == '\n' && last != '\r') {
+            *d++ = '\r';    // lf2crlf
+        } else if (ch == '.' && last == '\n') {
+            *d++ = '.';    // dotstuff
+        }
+        last = *d++ = ch;
+    }
+
+    result.truncate(d - result.data());
+    return result;
+}
+
+QByteArray TransferCommand::prepare(const QByteArray &ba)
+{
+    if (ba.isEmpty()) {
+        return 0;
+    }
+    if (mSMTP->lf2crlfAndDotStuffingRequested()) {
+        qCDebug(SMTP_LOG) << "performing dotstuffing and LF->CRLF transformation";
+        return dotstuff_lf2crlf(ba, mLastChar);
+    } else {
+        mLastChar = ba[ba.size() - 1];
+        return ba;
+    }
+}
+
+//
+// NOOP
+//
+
+QByteArray NoopCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mComplete = true;
+    mNeedResponse = true;
+    return "NOOP\r\n";
+}
+
+//
+// RSET
+//
+
+QByteArray RsetCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mComplete = true;
+    mNeedResponse = true;
+    return "RSET\r\n";
+}
+
+//
+// QUIT
+//
+
+QByteArray QuitCommand::nextCommandLine(TransactionState *ts)
+{
+    Q_UNUSED(ts)
+    mComplete = true;
+    mNeedResponse = true;
+    return "QUIT\r\n";
+}
+
+} // namespace KioSMTP
diff --git a/kioslave/src/smtp/command.h b/kioslave/src/smtp/command.h
new file mode 100644
index 0000000..a0b6a9c
--- /dev/null
+++ b/kioslave/src/smtp/command.h
@@ -0,0 +1,331 @@
+/*  -*- c++ -*-
+    command.h
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#ifndef __KIOSMTP_COMMAND_H__
+#define __KIOSMTP_COMMAND_H__
+
+extern "C" {
+#include <sasl/sasl.h>
+}
+
+#include <kio/authinfo.h>
+
+namespace KioSMTP
+{
+
+class Response;
+class TransactionState;
+class SMTPSessionInterface;
+
+/**
+ * @short Represents an SMTP command
+ *
+ * Semantics: A command consists of a series of "command lines"
+ * (though that's stretching it a bit for @ref TransferJob and @ref
+ * AuthCommand) and responses. There's typically one response for
+ * one command line and the command is completed.
+ *
+ * However, some commands consist of a dialog (command line,
+ * response, command line, response,...) where each successive
+ * command line is dependant on the previously received response
+ * (and thus those commands are not pipelinable). That's why each
+ * command signals completion by having it's @ref #isComplete()
+ * method return true @em after the last command line to be sent,
+ * but @em before the last response to receive. @ref AuthCommand is
+ * the principal representative of this kind of command. Because
+ * @ref EHLOCommand automatically falls back to HELO in case EHLO
+ * isn't supported, it is also of this kind. If completion is
+ * signalled before the first command line is issued, it is not to
+ * be executed at all.
+ *
+ * Other commands need to send multiple "command lines" before
+ * receiving a single (final) response. @ref TransferCommand is the
+ * only representative of this kind of "command". That's why each
+ * command signals whether it now expects a response before being
+ * able to issue the next command line (if any) by having it's @ref
+ * #needsResponse() method return true.
+ *
+ * Commands whose @ref #nextCommandLine() does not support being
+ * called multiple times in a row without changing command state,
+ * must reimplement @ref #ungetCommandLine().
+ **/
+class Command
+{
+public:
+    enum Flags {
+        OnlyLastInPipeline = 1,
+        OnlyFirstInPipeline = 2,
+        CloseConnectionOnError = 4
+    };
+
+    explicit Command(SMTPSessionInterface *smtp, int flags = 0);
+    virtual ~Command();
+
+    enum Type {
+        STARTTLS,
+        DATA,
+        NOOP,
+        RSET,
+        QUIT
+    };
+
+    static Command *createSimpleCommand(int which, SMTPSessionInterface *smtp);
+
+    virtual QByteArray nextCommandLine(TransactionState *ts = 0) = 0;
+    /* Reimplement this if your @ref #nextCommandLine() implementation
+       changes state other than @ref mComplete. The default
+       implementation just resets @ref mComplete to false. */
+    virtual void ungetCommandLine(const QByteArray &cmdLine, TransactionState *ts = 0);
+    /* Reimplement this if your command need more sophisicated
+       response processing than just checking for @ref
+       Response::isOk(). The default implementation sets @ref
+       mComplete to true, @ref mNeedResponse to false and returns
+       whether the response isOk(). */
+    virtual bool processResponse(const Response &response, TransactionState *ts = 0);
+
+    virtual bool doNotExecute(const TransactionState *ts) const
+    {
+        Q_UNUSED(ts)
+        return false;
+    }
+
+    bool isComplete() const
+    {
+        return mComplete;
+    }
+
+    /**
+     * @return whether the command expects a response now. Some
+     * commands (most notably AUTH) may consist of a series of
+     * commands and associated responses until they are
+     * complete. Others (most notably @ref TransferCommand usually
+     * send multiple "command lines" before expecting a response.
+     */
+    bool needsResponse() const
+    {
+        return mNeedResponse;
+    }
+
+    /**
+     * @return whether an error in executing this command is so fatal
+     * that closing the connection is the only option
+     */
+    bool closeConnectionOnError() const
+    {
+        return mFlags & CloseConnectionOnError;
+    }
+    bool mustBeLastInPipeline() const
+    {
+        return mFlags & OnlyLastInPipeline;
+    }
+    bool mustBeFirstInPipeline() const
+    {
+        return mFlags & OnlyFirstInPipeline;
+    }
+
+protected:
+    SMTPSessionInterface *mSMTP;
+    bool mComplete;
+    bool mNeedResponse;
+    const int mFlags;
+
+protected:
+    // only relay methods to enable access to slave-protected methods
+    // for subclasses of Command:
+    void parseFeatures(const Response &r);
+    int startSsl();
+    bool haveCapability(const char *cap) const;
+};
+
+class EHLOCommand : public Command
+{
+public:
+    EHLOCommand(SMTPSessionInterface *smtp, const QString &hostname)
+        : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline)
+        , mEHLONotSupported(false)
+        , mHostname(hostname.trimmed())
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+    bool processResponse(const Response &response, TransactionState *ts) Q_DECL_OVERRIDE;
+private:
+    bool mEHLONotSupported;
+    QString mHostname;
+};
+
+class StartTLSCommand : public Command
+{
+public:
+    StartTLSCommand(SMTPSessionInterface *smtp)
+        : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline)
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+    bool processResponse(const Response &response, TransactionState *ts) Q_DECL_OVERRIDE;
+};
+
+class AuthCommand : public Command
+{
+public:
+    AuthCommand(SMTPSessionInterface *smtp, const char *mechanisms,
+                const QString &aFQDN, KIO::AuthInfo &ai);
+    ~AuthCommand();
+    bool doNotExecute(const TransactionState *ts) const Q_DECL_OVERRIDE;
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+    void ungetCommandLine(const QByteArray &cmdLine, TransactionState *ts) Q_DECL_OVERRIDE;
+    bool processResponse(const Response &response, TransactionState *ts) Q_DECL_OVERRIDE;
+private:
+    bool saslInteract(void *in);
+
+    sasl_conn_t *conn;
+    sasl_interact_t *client_interact;
+    const char *mOut;
+    uint mOutlen;
+    bool mOneStep;
+
+    const char *mMechusing;
+    KIO::AuthInfo *mAi;
+    QByteArray mLastChallenge;
+    QByteArray mUngetSASLResponse;
+    bool mFirstTime;
+};
+
+class MailFromCommand : public Command
+{
+public:
+    MailFromCommand(SMTPSessionInterface *smtp, const QByteArray &addr,
+                    bool eightBit = false, unsigned int size = 0)
+        : Command(smtp)
+        , mAddr(addr)
+        , m8Bit(eightBit)
+        , mSize(size)
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+    bool processResponse(const Response &response, TransactionState *ts) Q_DECL_OVERRIDE;
+private:
+    QByteArray mAddr;
+    bool m8Bit;
+    unsigned int mSize;
+};
+
+class RcptToCommand : public Command
+{
+public:
+    RcptToCommand(SMTPSessionInterface *smtp, const QByteArray &addr)
+        : Command(smtp)
+        , mAddr(addr)
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+    bool processResponse(const Response &response, TransactionState *ts) Q_DECL_OVERRIDE;
+private:
+    QByteArray mAddr;
+};
+
+/** Handles only the initial intermediate response and compltetes at
+    the point where the mail contents need to be sent */
+class DataCommand : public Command
+{
+public:
+    DataCommand(SMTPSessionInterface *smtp)
+        : Command(smtp, OnlyLastInPipeline)
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+    void ungetCommandLine(const QByteArray &cmd, TransactionState *ts) Q_DECL_OVERRIDE;
+    bool processResponse(const Response &response, TransactionState *ts) Q_DECL_OVERRIDE;
+};
+
+/** Handles the data transfer following a successful DATA command */
+class TransferCommand : public Command
+{
+public:
+    TransferCommand(SMTPSessionInterface *smtp, const QByteArray &initialBuffer)
+        : Command(smtp, OnlyFirstInPipeline)
+        , mUngetBuffer(initialBuffer)
+        , mLastChar('\n')
+        , mWasComplete(false)
+    {
+    }
+
+    bool doNotExecute(const TransactionState *ts) const Q_DECL_OVERRIDE;
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+    void ungetCommandLine(const QByteArray &cmd, TransactionState *ts) Q_DECL_OVERRIDE;
+    bool processResponse(const Response &response, TransactionState *ts) Q_DECL_OVERRIDE;
+private:
+    QByteArray prepare(const QByteArray &ba);
+    QByteArray mUngetBuffer;
+    char mLastChar;
+    bool mWasComplete; // ... before ungetting
+};
+
+class NoopCommand : public Command
+{
+public:
+    NoopCommand(SMTPSessionInterface *smtp)
+        : Command(smtp, OnlyLastInPipeline)
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+};
+
+class RsetCommand : public Command
+{
+public:
+    RsetCommand(SMTPSessionInterface *smtp)
+        : Command(smtp, CloseConnectionOnError)
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+};
+
+class QuitCommand : public Command
+{
+public:
+    QuitCommand(SMTPSessionInterface *smtp)
+        : Command(smtp, CloseConnectionOnError | OnlyLastInPipeline)
+    {
+    }
+
+    QByteArray nextCommandLine(TransactionState *ts) Q_DECL_OVERRIDE;
+};
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_COMMAND_H__
diff --git a/kioslave/src/smtp/compliance.txt b/kioslave/src/smtp/compliance.txt
new file mode 100644
index 0000000..b6b9874
--- /dev/null
+++ b/kioslave/src/smtp/compliance.txt
@@ -0,0 +1,33 @@
+The SMTP kioslave currently conforms to the following SMTP-related RFCs:
+
+Base Spec:
+2821 Simple Mail Transfer Protocol. J. Klensin, Ed.. April 2001.
+     (Format: TXT=192504 bytes) (Obsoletes RFC0821, RFC0974, RFC1869)
+     (Status: PROPOSED STANDARD)
+
+Encryption/Auth:
+3207 SMTP Service Extension for Secure SMTP over Transport Layer
+     Security. P. Hoffman. February 2002. (Format: TXT=18679 bytes)
+     (Obsoletes RFC2487) (Status: PROPOSED STANDARD)
+
+2554 SMTP Service Extension for Authentication. J. Myers. March 1999.
+     (Format: TXT=20534 bytes) (Status: PROPOSED STANDARD)
+(with all SASL mechanisms supported by KDESasl)
+
+General:
+1652 SMTP Service Extension for 8bit-MIMEtransport. J. Klensin, N.
+     Freed, M. Rose, E. Stefferud, D. Crocker. July 1994. (Format:
+     TXT=11842 bytes) (Obsoletes RFC1426) (Status: DRAFT STANDARD)
+
+1870 SMTP Service Extension for Message Size Declaration. J. Klensin,
+     N. Freed, K. Moore. November 1995. (Format: TXT=18226 bytes)
+     (Obsoletes RFC1653) (Also STD0010) (Status: STANDARD)
+
+2920 SMTP Service Extension for Command Pipelining. N. Freed.
+     September 2000. (Format: TXT=17065 bytes) (Obsoletes RFC2197) (Also
+     STD0060) (Status: STANDARD)
+
+Known shortcomings:
+- Doesn't enforce the CRLF lineending convention on user-supplied data.
+- Due to the lack of a Mulit_Put_ in the KIO infrastructure, pipelining
+  across messages isn't supported.
diff --git a/kioslave/src/smtp/kioslavesession.cpp b/kioslave/src/smtp/kioslavesession.cpp
new file mode 100644
index 0000000..3c770bd
--- /dev/null
+++ b/kioslave/src/smtp/kioslavesession.cpp
@@ -0,0 +1,88 @@
+/*
+    Copyright (c) 2010 Volker Krause <vkrause at 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 "kioslavesession.h"
+
+using namespace KioSMTP;
+
+KioSMTP::KioSlaveSession::KioSlaveSession(SMTPProtocol *protocol)
+    : m_protocol(protocol)
+{
+}
+
+void KioSMTP::KioSlaveSession::error(int id, const QString &msg)
+{
+    m_protocol->error(id, msg);
+}
+
+void KioSlaveSession::informationMessageBox(const QString &msg, const QString &caption)
+{
+    m_protocol->messageBox(KIO::SlaveBase::Information, msg, caption);
+}
+
+bool KioSMTP::KioSlaveSession::openPasswordDialog(KIO::AuthInfo &authInfo)
+{
+    return m_protocol->openPasswordDialog(authInfo);
+}
+
+void KioSMTP::KioSlaveSession::dataReq()
+{
+    m_protocol->dataReq();
+}
+
+int KioSMTP::KioSlaveSession::readData(QByteArray &ba)
+{
+    return m_protocol->readData(ba);
+}
+
+bool KioSMTP::KioSlaveSession::startSsl()
+{
+    return m_protocol->startSsl();
+}
+
+bool KioSlaveSession::eightBitMimeRequested() const
+{
+    return m_protocol->metaData(QStringLiteral("8bitmime")) == QLatin1String("on");
+}
+
+bool KioSlaveSession::lf2crlfAndDotStuffingRequested() const
+{
+    return m_protocol->metaData(QStringLiteral("lf2crlf+dotstuff")) == QLatin1String("slave");
+}
+
+bool KioSlaveSession::pipeliningRequested() const
+{
+    return m_protocol->metaData(QStringLiteral("pipelining")) != QLatin1String("off");
+}
+
+QString KioSlaveSession::requestedSaslMethod() const
+{
+    return m_protocol->metaData(QStringLiteral("sasl"));
+}
+
+KioSMTP::SMTPSessionInterface::TLSRequestState KioSMTP::KioSlaveSession::tlsRequested() const
+{
+    if (m_protocol->metaData(QStringLiteral("tls")) == QLatin1String("off")) {
+        return ForceNoTLS;
+    }
+    if (m_protocol->metaData(QStringLiteral("tls")) == QLatin1String("on")) {
+        return ForceTLS;
+    }
+    return UseTLSIfAvailable;
+}
diff --git a/kioslave/src/smtp/kioslavesession.h b/kioslave/src/smtp/kioslavesession.h
new file mode 100644
index 0000000..13118c0
--- /dev/null
+++ b/kioslave/src/smtp/kioslavesession.h
@@ -0,0 +1,52 @@
+/*
+    Copyright (c) 2010 Volker Krause <vkrause at 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 KIOSMTP_KIOSLAVESESSION_H
+#define KIOSMTP_KIOSLAVESESSION_H
+
+#include "smtpsessioninterface.h"
+#include "smtp.h"
+
+namespace KioSMTP
+{
+
+class KioSlaveSession : public SMTPSessionInterface
+{
+public:
+    explicit KioSlaveSession(SMTPProtocol *protocol);
+    void error(int id, const QString &msg) Q_DECL_OVERRIDE;
+    void informationMessageBox(const QString &msg, const QString &caption) Q_DECL_OVERRIDE;
+    bool openPasswordDialog(KIO::AuthInfo &authInfo) Q_DECL_OVERRIDE;
+    void dataReq() Q_DECL_OVERRIDE;
+    int readData(QByteArray &ba) Q_DECL_OVERRIDE;
+    bool startSsl() Q_DECL_OVERRIDE;
+
+    QString requestedSaslMethod() const Q_DECL_OVERRIDE;
+    bool eightBitMimeRequested() const Q_DECL_OVERRIDE;
+    bool lf2crlfAndDotStuffingRequested() const Q_DECL_OVERRIDE;
+    bool pipeliningRequested() const Q_DECL_OVERRIDE;
+    TLSRequestState tlsRequested() const Q_DECL_OVERRIDE;
+
+private:
+    SMTPProtocol *m_protocol;
+};
+
+}
+
+#endif
diff --git a/kioslave/src/smtp/request.cpp b/kioslave/src/smtp/request.cpp
new file mode 100644
index 0000000..509bd95
--- /dev/null
+++ b/kioslave/src/smtp/request.cpp
@@ -0,0 +1,196 @@
+/*  -*- c++ -*-
+    request.cc
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#include "request.h"
+#include "smtp_debug.h"
+
+#include <QUrl>
+#include <QtCore/QUrl>
+#include <qdebug.h>
+
+#include <assert.h>
+
+namespace KioSMTP
+{
+
+Request Request::fromURL(const QUrl &url)
+{
+    Request request;
+
+    const QStringList query = url.query().split(QLatin1Char('&'));
+#ifndef NDEBUG
+    qCDebug(SMTP_LOG) << "Parsing request from query:\n" << query.join(QStringLiteral("\n"));
+#endif
+    for (QStringList::const_iterator it = query.begin(); it != query.end(); ++it) {
+        int equalsPos = (*it).indexOf(QLatin1Char('='));
+        if (equalsPos <= 0) {
+            continue;
+        }
+
+        const QString key = (*it).left(equalsPos).toLower();
+        const QString value = QUrl::fromPercentEncoding((*it).mid(equalsPos + 1).toLatin1());     //krazy:exclude=qclasses
+
+        if (key == QLatin1String("to")) {
+            request.addTo(value);
+        } else if (key == QLatin1String("cc")) {
+            request.addCc(value);
+        } else if (key == QLatin1String("bcc")) {
+            request.addBcc(value);
+        } else if (key == QLatin1String("headers")) {
+            request.setEmitHeaders(value == QLatin1String("0"));
+            request.setEmitHeaders(false);   // ### ???
+        } else if (key == QLatin1String("subject")) {
+            request.setSubject(value);
+        } else if (key == QLatin1String("from")) {
+            request.setFromAddress(value);
+        } else if (key == QLatin1String("profile")) {
+            request.setProfileName(value);
+        } else if (key == QLatin1String("hostname")) {
+            request.setHeloHostname(value);
+        } else if (key == QLatin1String("body")) {
+            request.set8BitBody(value.toUpper() == QLatin1String("8BIT"));
+        } else if (key == QLatin1String("size")) {
+            request.setSize(value.toUInt());
+        } else {
+            qCWarning(SMTP_LOG) << "while parsing query: unknown query item \""
+                                << key << "\" with value \"" << value << "\"" << endl;
+        }
+    }
+
+    return request;
+}
+
+QByteArray Request::heloHostnameCString() const
+{
+    return QUrl::toAce(heloHostname());   //krazy:exclude=qclasses
+}
+
+static bool isUsAscii(const QString &s)
+{
+    for (int i = 0; i < s.length(); ++i) {
+        if (s[i].unicode() > 127) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static inline bool isSpecial(char ch)
+{
+    static const QByteArray specials = "()<>[]:;@\\,.\"";
+    return specials.indexOf(ch) >= 0;
+}
+
+static inline bool needsQuoting(char ch)
+{
+    return ch == '\\' || ch == '"' || ch == '\n';
+}
+
+static inline QByteArray rfc2047Encode(const QString &s)
+{
+    QByteArray r = s.trimmed().toUtf8().toBase64();
+    return "=?utf-8?b?" + r + "?="; // use base64 since that always gives a valid encoded-word
+}
+
+static QByteArray quote(const QString &s)
+{
+    assert(isUsAscii(s));
+
+    QByteArray r(s.length() * 2, 0);
+    bool needsQuotes = false;
+
+    unsigned int j = 0;
+    for (int i = 0; i < s.length(); ++i) {
+        char ch = s[i].toLatin1();
+        if (isSpecial(ch)) {
+            if (needsQuoting(ch)) {
+                r[j++] = '\\';
+            }
+            needsQuotes = true;
+        }
+        r[j++] = ch;
+    }
+    r.truncate(j);
+
+    if (needsQuotes) {
+        return '"' + r + '"';
+    } else {
+        return r;
+    }
+}
+
+static QByteArray formatFromAddress(const QString &fromRealName, const QString &fromAddress)
+{
+    if (fromRealName.isEmpty()) {
+        return fromAddress.toLatin1();    // no real name: return "joe at user.org"
+    }
+
+    // return "Joe User <joe at user.org>", "\"User, Joe\" <joe at user.org>"
+    // or "=?utf-8?q?Joe_User?= <joe at user.org>", depending on real name's nature.
+    QByteArray r = isUsAscii(fromRealName) ? quote(fromRealName) : rfc2047Encode(fromRealName);
+    return r + " <" + fromAddress.toLatin1() + '>';
+}
+
+static QByteArray formatSubject(QString s)
+{
+    if (isUsAscii(s)) {
+        return s.remove(QLatin1Char('\n')).toLatin1();    // don't break header folding,
+    } else {
+        // so remove any line break
+        // that happen to be around
+        return rfc2047Encode(s);
+    }
+}
+
+QByteArray Request::headerFields(const QString &fromRealName) const
+{
+    if (!emitHeaders()) {
+        return 0;
+    }
+
+    assert(hasFromAddress());   // should have been checked for by
+    // caller (MAIL FROM comes before DATA)
+
+    QByteArray result = "From: " + formatFromAddress(fromRealName, fromAddress()) + "\r\n";
+
+    if (!subject().isEmpty()) {
+        result += "Subject: " + formatSubject(subject()) + "\r\n";
+    }
+    if (!to().empty()) {
+        result += QByteArray("To: ") + to().join(QStringLiteral(",\r\n\t") /* line folding */).toLatin1() + "\r\n";
+    }
+    if (!cc().empty()) {
+        result += QByteArray("Cc: ") + cc().join(QStringLiteral(",\r\n\t") /* line folding */).toLatin1() + "\r\n";
+    }
+    return result;
+}
+
+} // namespace KioSMTP
diff --git a/kioslave/src/smtp/request.h b/kioslave/src/smtp/request.h
new file mode 100644
index 0000000..cd399d3
--- /dev/null
+++ b/kioslave/src/smtp/request.h
@@ -0,0 +1,180 @@
+/*  -*- c++ -*-
+    request.h
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#ifndef __KIOSMTP_REQUEST_H__
+#define __KIOSMTP_REQUEST_H__
+
+#include <QStringList>
+#include <QByteArray>
+
+class QUrl;
+
+namespace KioSMTP
+{
+
+class Request
+{
+public:
+    Request()
+        : mSubject(QStringLiteral("missing subject"))
+        , mEmitHeaders(true)
+        , m8Bit(false)
+        , mSize(0)
+    {
+    }
+
+    static Request fromURL(const QUrl &url);
+
+    QString profileName() const
+    {
+        return mProfileName;
+    }
+    void setProfileName(const QString &profileName)
+    {
+        mProfileName = profileName;
+    }
+    bool hasProfile() const
+    {
+        return !profileName().isNull();
+    }
+
+    QString subject() const
+    {
+        return mSubject;
+    }
+    void setSubject(const QString &subject)
+    {
+        mSubject = subject;
+    }
+
+    QString fromAddress() const
+    {
+        return mFromAddress;
+    }
+    void setFromAddress(const QString &fromAddress)
+    {
+        mFromAddress = fromAddress;
+    }
+    bool hasFromAddress() const
+    {
+        return !mFromAddress.isEmpty();
+    }
+
+    QStringList recipients() const
+    {
+        return to() + cc() + bcc();
+    }
+    bool hasRecipients() const
+    {
+        return !to().empty() || !cc().empty() || !bcc().empty();
+    }
+
+    QStringList to() const
+    {
+        return mTo;
+    }
+    QStringList cc() const
+    {
+        return mCc;
+    }
+    QStringList bcc() const
+    {
+        return mBcc;
+    }
+    void addTo(const QString &to)
+    {
+        mTo.push_back(to);
+    }
+    void addCc(const QString &cc)
+    {
+        mCc.push_back(cc);
+    }
+    void addBcc(const QString &bcc)
+    {
+        mBcc.push_back(bcc);
+    }
+
+    QString heloHostname() const
+    {
+        return mHeloHostname;
+    }
+    QByteArray heloHostnameCString() const;
+    void setHeloHostname(const QString &hostname)
+    {
+        mHeloHostname = hostname;
+    }
+
+    bool emitHeaders() const
+    {
+        return mEmitHeaders;
+    }
+    void setEmitHeaders(bool emitHeaders)
+    {
+        mEmitHeaders = emitHeaders;
+    }
+
+    bool is8BitBody() const
+    {
+        return m8Bit;
+    }
+    void set8BitBody(bool a8Bit)
+    {
+        m8Bit = a8Bit;
+    }
+
+    unsigned int size() const
+    {
+        return mSize;
+    }
+    void setSize(unsigned int size)
+    {
+        mSize = size;
+    }
+
+    /**
+     * If @ref #emitHeaders() is true, returns the rfc2822
+     * serialization of the header fields "To", "Cc", "Subject" and
+     * "From", as determined by the respective settings. If @ref
+     * #emitHeaders() is false, returns a null string.
+     */
+    QByteArray headerFields(const QString &fromRealName = QString()) const;
+
+private:
+    QStringList mTo, mCc, mBcc;
+    QString mProfileName, mSubject, mFromAddress, mHeloHostname;
+    bool mEmitHeaders;
+    bool m8Bit;
+    unsigned int mSize;
+};
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_REQUEST_H__
diff --git a/kioslave/src/smtp/response.cpp b/kioslave/src/smtp/response.cpp
new file mode 100644
index 0000000..e0a730f
--- /dev/null
+++ b/kioslave/src/smtp/response.cpp
@@ -0,0 +1,169 @@
+/*  -*- c++ -*-
+    response.cc
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#include "response.h"
+
+#include <KLocalizedString>
+#include <kio/global.h>
+
+#include <QByteArray>
+
+namespace KioSMTP
+{
+
+void Response::parseLine(const char *line, int len)
+{
+
+    if (!isWellFormed()) {
+        return;    // don't bother
+    }
+
+    if (isComplete()) {
+        // if the response is already complete, there can't be another line
+        mValid = false;
+    }
+
+    if (len > 1 && line[len - 1] == '\n' && line[len - 2] == '\r') {
+        len -= 2;
+    }
+
+    if (len < 3) {
+        // can't be valid - too short
+        mValid = false;
+        mWellFormed = false;
+        return;
+    }
+
+    bool ok = false;
+    unsigned int code = QByteArray(line, 3).toUInt(&ok);
+    if (!ok || code < 100 || code > 559) {
+        // not a number or number out of range
+        mValid = false;
+        if (!ok || code < 100) {
+            mWellFormed = false;
+        }
+        return;
+    }
+    if (mCode && code != mCode) {
+        // different codes in one response are not allowed.
+        mValid = false;
+        return;
+    }
+    mCode = code;
+
+    if (len == 3 || line[3] == ' ') {
+        mSawLastLine = true;
+    } else if (line[3] != '-') {
+        // code must be followed by either SP or hyphen (len == 3 is
+        // also accepted since broken servers exist); all else is
+        // invalid
+        mValid = false;
+        mWellFormed = false;
+        return;
+    }
+
+    mLines.push_back(len > 4 ? QByteArray(line + 4, len - 4).trimmed() : QByteArray());
+}
+
+// hackishly fixing QCStringList flaws...
+static QByteArray join(char sep, const QCStringList &list)
+{
+    if (list.empty()) {
+        return QByteArray();
+    }
+    QByteArray result = list.front();
+    for (QCStringList::const_iterator it = ++list.begin(); it != list.end(); ++it) {
+        result += sep + *it;
+    }
+    return result;
+}
+
+QString Response::errorMessage() const
+{
+    QString msg;
+    if (lines().count() > 1) {
+        msg = i18n("The server responded:\n%1", QString::fromLatin1(join('\n', lines())));
+    } else {
+        msg = i18n("The server responded: \"%1\"", QString::fromLatin1(lines().at(0)));
+    }
+    if (first() == 4) {
+        msg += QLatin1Char('\n') + i18n("This is a temporary failure. You may try again later.");
+    }
+    return msg;
+}
+
+int Response::errorCode() const
+{
+    switch (code()) {
+    case 421: // Service not available, closing transmission channel
+    case 454: // TLS not available due to temporary reason
+    // Temporary authentication failure
+    case 554: // Transaction failed / No SMTP service here / No valid recipients
+        return KIO::ERR_SERVICE_NOT_AVAILABLE;
+
+    case 451: // Requested action aborted: local error in processing
+        return KIO::ERR_INTERNAL_SERVER;
+
+    case 452: // Requested action not taken: insufficient system storage
+    case 552: // Requested mail action aborted: exceeded storage allocation
+        return KIO::ERR_DISK_FULL;
+
+    case 500: // Syntax error, command unrecognized
+    case 501: // Syntax error in parameters or arguments
+    case 502: // Command not implemented
+    case 503: // Bad sequence of commands
+    case 504: // Command parameter not implemented
+        return KIO::ERR_INTERNAL;
+
+    case 450: // Requested mail action not taken: mailbox unavailable
+    case 550: // Requested action not taken: mailbox unavailable
+    case 551: // User not local; please try <forward-path>
+    case 553: // Requested action not taken: mailbox name not allowed
+        return KIO::ERR_DOES_NOT_EXIST;
+
+    case 530: // {STARTTLS,Authentication} required
+    case 538: // Encryption required for requested authentication mechanism
+    case 534: // Authentication mechanism is too weak
+        return KIO::ERR_UPGRADE_REQUIRED;
+
+    case 432: // A password transition is needed
+        return KIO::ERR_COULD_NOT_AUTHENTICATE;
+
+    default:
+        if (isPositive()) {
+            return 0;
+        } else {
+            return KIO::ERR_UNKNOWN;
+        }
+    }
+}
+
+} // namespace KioSMTP
diff --git a/kioslave/src/smtp/response.h b/kioslave/src/smtp/response.h
new file mode 100644
index 0000000..b182377
--- /dev/null
+++ b/kioslave/src/smtp/response.h
@@ -0,0 +1,173 @@
+/*  -*- c++ -*-
+    response.h
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#ifndef __KIOSMTP_RESPONSE_H__
+#define __KIOSMTP_RESPONSE_H__
+
+#include <QList>
+#include <QString>
+typedef QList<QByteArray> QCStringList;
+
+class QString;
+
+namespace KioSMTP
+{
+
+class Response
+{
+public:
+    Response()
+        : mCode(0)
+        , mValid(true)
+        , mSawLastLine(false)
+        , mWellFormed(true)
+    {
+    }
+
+    void parseLine(const char *line)
+    {
+        parseLine(line, qstrlen(line));
+    }
+    void parseLine(const char *line, int len);
+
+    /** Return an internationalized error message according to the
+     *  response's code. */
+    QString errorMessage() const;
+    /** Translate the SMTP error code into a KIO one */
+    int errorCode() const;
+
+    enum Reply {
+        UnknownReply = -1,
+        PositivePreliminary = 1,
+        PositiveCompletion = 2,
+        PositiveIntermediate = 3,
+        TransientNegative = 4,
+        PermanentNegative = 5
+    };
+
+    enum Category {
+        UnknownCategory = -1,
+        SyntaxError = 0,
+        Information = 1,
+        Connections = 2,
+        MailSystem = 5
+    };
+
+    unsigned int code() const
+    {
+        return mCode;
+    }
+    unsigned int first() const
+    {
+        return code() / 100;
+    }
+    unsigned int second() const
+    {
+        return (code() % 100) / 10;
+    }
+    unsigned int third() const
+    {
+        return code() % 10;
+    }
+
+    bool isPositive() const
+    {
+        return first() <= 3 && first() >= 1;
+    }
+    bool isNegative() const
+    {
+        return first() == 4 || first() == 5;
+    }
+    bool isUnknown() const
+    {
+        return !isPositive() && !isNegative();
+    }
+
+    QCStringList lines() const
+    {
+        return mLines;
+    }
+
+    bool isValid() const
+    {
+        return mValid;
+    }
+    bool isComplete() const
+    {
+        return mSawLastLine;
+    }
+
+    /** Shortcut method.
+     *  @return true iff the response is valid, complete and positive
+     */
+    bool isOk() const
+    {
+        return isValid() && isComplete() && isPositive();
+    }
+
+    /** Indicates whether the response was well-formed, meaning it
+     *  obeyed the syntax of smtp responses. That the response
+     *  nevertheless is not valid may be caused by e.g. different
+     *  response codes in a multilie response. A non-well-formed
+     *  response is never valid.
+     */
+    bool isWellFormed() const
+    {
+        return mWellFormed;
+    }
+
+    void clear()
+    {
+        *this = Response();
+    }
+
+#ifdef KIOSMTP_COMPARATORS
+    bool operator==(const Response &other) const
+    {
+        return mCode == other.mCode &&
+               mValid == other.mValid &&
+               mSawLastLine == other.mSawLastLine &&
+               mWellFormed == other.mWellFormed &&
+               mLines == other.mLines;
+    }
+#endif
+
+private:
+    unsigned int mCode;
+    QCStringList mLines;
+    bool mValid;
+    bool mSawLastLine;
+    bool mWellFormed;
+};
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_RESPONSE_H__
diff --git a/kioslave/src/smtp/smtp.cpp b/kioslave/src/smtp/smtp.cpp
new file mode 100644
index 0000000..e6e6dd0
--- /dev/null
+++ b/kioslave/src/smtp/smtp.cpp
@@ -0,0 +1,654 @@
+/*
+ * Copyright (c) 2000, 2001 Alex Zepeda <zipzippy at sonic.net>
+ * Copyright (c) 2001 Michael H�kel <Michael at Haeckel.Net>
+ * Copyright (c) 2002 Aaron J. Seigo <aseigo at olympusproject.org>
+ * Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "smtp.h"
+#include "smtp_debug.h"
+
+extern "C" {
+#include <sasl/sasl.h>
+}
+
+#include "../common.h"
+#include "request.h"
+#include "response.h"
+#include "transactionstate.h"
+#include "command.h"
+#include "kioslavesession.h"
+using KioSMTP::Capabilities;
+using KioSMTP::Command;
+using KioSMTP::EHLOCommand;
+using KioSMTP::AuthCommand;
+using KioSMTP::MailFromCommand;
+using KioSMTP::RcptToCommand;
+using KioSMTP::DataCommand;
+using KioSMTP::TransferCommand;
+using KioSMTP::Request;
+using KioSMTP::Response;
+using KioSMTP::TransactionState;
+using KioSMTP::SMTPSessionInterface;
+
+#include <kemailsettings.h>
+
+#include <qdebug.h>
+#include <kio/slaveinterface.h>
+#include <KLocalizedString>
+#include <QUrl>
+
+#include <QHostInfo>
+#include <QDataStream>
+
+#include <memory>
+using std::unique_ptr;
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <netdb.h>
+
+extern "C" {
+    Q_DECL_EXPORT int kdemain(int argc, char **argv);
+}
+
+int kdemain(int argc, char **argv)
+{
+    QCoreApplication app(argc, argv);
+    app.setApplicationName(QStringLiteral("kio_smtp"));
+
+    if (argc != 4) {
+        fprintf(stderr,
+                "Usage: kio_smtp protocol domain-socket1 domain-socket2\n");
+        exit(-1);
+    }
+
+    if (!initSASL()) {
+        exit(-1);
+    }
+    SMTPProtocol slave(argv[2], argv[3], qstricmp(argv[1], "smtps") == 0);
+    slave.dispatchLoop();
+    sasl_done();
+    return 0;
+}
+
+SMTPProtocol::SMTPProtocol(const QByteArray &pool, const QByteArray &app,
+                           bool useSSL)
+    : TCPSlaveBase(useSSL ? "smtps" : "smtp", pool, app, useSSL)
+    , m_sOldPort(0)
+    , m_opened(false)
+    , m_sessionIface(new KioSMTP::KioSlaveSession(this))
+{
+    //qCDebug(SMTP_LOG) << "SMTPProtocol::SMTPProtocol";
+}
+
+SMTPProtocol::~SMTPProtocol()
+{
+    //qCDebug(SMTP_LOG) << "SMTPProtocol::~SMTPProtocol";
+    smtp_close();
+    delete m_sessionIface;
+}
+
+void SMTPProtocol::openConnection()
+{
+
+    // Don't actually call smtp_open() yet. Just pretend that we are connected.
+    // We can't call smtp_open() here, as that does EHLO, and the EHLO command
+    // needs the fake hostname. However, we only get the fake hostname in put(), so
+    // we call smtp_open() there.
+    connected();
+}
+
+void SMTPProtocol::closeConnection()
+{
+    smtp_close();
+}
+
+void SMTPProtocol::special(const QByteArray &aData)
+{
+    QDataStream s(aData);
+    int what;
+    s >> what;
+    if (what == 'c') {
+        const QString response = m_sessionIface->capabilities().createSpecialResponse(
+                                     (isUsingSsl() && !isAutoSsl())
+                                     || m_sessionIface->haveCapability("STARTTLS"));
+        infoMessage(response);
+    } else if (what == 'N') {
+        if (!execute(Command::NOOP)) {
+            return;
+        }
+    } else {
+        error(KIO::ERR_INTERNAL, i18n("The application sent an invalid request."));
+        return;
+    }
+    finished();
+}
+
+// Usage: smtp://smtphost:port/send?to=user@host.com&subject=blah
+// If smtphost is the name of a profile, it'll use the information
+// provided by that profile.  If it's not a profile name, it'll use it as
+// nature intended.
+// One can also specify in the query:
+// headers=0 (turns off header generation)
+// to=emailaddress
+// cc=emailaddress
+// bcc=emailaddress
+// subject=text
+// profile=text (this will override the "host" setting)
+// hostname=text (used in the HELO)
+// body={7bit,8bit} (default: 7bit; 8bit activates the use of the 8BITMIME SMTP extension)
+void SMTPProtocol::put(const QUrl &url, int permissions, KIO::JobFlags)
+{
+    Request request = Request::fromURL(url);   // parse settings from URL's query
+
+    KEMailSettings mset;
+    QUrl open_url = url;
+    if (!request.hasProfile()) {
+        //qCDebug(SMTP_LOG) << "kio_smtp: Profile is null";
+        bool hasProfile = mset.profiles().contains(open_url.host());
+        if (hasProfile) {
+            mset.setProfile(open_url.host());
+            open_url.setHost(mset.getSetting(KEMailSettings::OutServer));
+            m_sUser = mset.getSetting(KEMailSettings::OutServerLogin);
+            m_sPass = mset.getSetting(KEMailSettings::OutServerPass);
+
+            if (m_sUser.isEmpty()) {
+                m_sUser.clear();
+            }
+            if (m_sPass.isEmpty()) {
+                m_sPass.clear();
+            }
+            open_url.setUserName(m_sUser);
+            open_url.setPassword(m_sPass);
+            m_sServer = open_url.host();
+            m_port = open_url.port();
+        } else {
+            mset.setProfile(mset.defaultProfileName());
+        }
+    } else {
+        mset.setProfile(request.profileName());
+    }
+
+    // Check KEMailSettings to see if we've specified an E-Mail address
+    // if that worked, check to see if we've specified a real name
+    // and then format accordingly (either: emailaddress at host.com or
+    // Real Name <emailaddress at host.com>)
+    if (!request.hasFromAddress()) {
+        const QString from = mset.getSetting(KEMailSettings::EmailAddress);
+        if (!from.isNull()) {
+            request.setFromAddress(from);
+        } else if (request.emitHeaders()) {
+            error(KIO::ERR_NO_CONTENT, i18n("The sender address is missing."));
+            return;
+        }
+    }
+
+    if (!smtp_open(request.heloHostname())) {
+        error(KIO::ERR_SERVICE_NOT_AVAILABLE,
+              i18n("SMTPProtocol::smtp_open failed (%1)", // ### better error message?
+                   open_url.path()));
+        return;
+    }
+
+    if (request.is8BitBody()
+            && !m_sessionIface->haveCapability("8BITMIME") && !m_sessionIface->eightBitMimeRequested()) {
+        error(KIO::ERR_SERVICE_NOT_AVAILABLE,
+              i18n("Your server (%1) does not support sending of 8-bit messages.\n"
+                   "Please use base64 or quoted-printable encoding.", m_sServer));
+        return;
+    }
+
+    queueCommand(new MailFromCommand(m_sessionIface, request.fromAddress().toLatin1(),
+                                     request.is8BitBody(), request.size()));
+
+    // Loop through our To and CC recipients, and send the proper
+    // SMTP commands, for the benefit of the server.
+    const QStringList recipients = request.recipients();
+    for (QStringList::const_iterator it = recipients.begin(); it != recipients.end(); ++it) {
+        queueCommand(new RcptToCommand(m_sessionIface, (*it).toLatin1()));
+    }
+
+    queueCommand(Command::DATA);
+    queueCommand(new TransferCommand(m_sessionIface, request.headerFields(mset.getSetting(KEMailSettings::RealName))));
+
+    TransactionState ts;
+    if (!executeQueuedCommands(&ts)) {
+        if (ts.errorCode()) {
+            error(ts.errorCode(), ts.errorMessage());
+        }
+    } else {
+        finished();
+    }
+}
+
+void SMTPProtocol::setHost(const QString &host, quint16 port,
+                           const QString &user, const QString &pass)
+{
+    m_sServer = host;
+    m_port = port;
+    m_sUser = user;
+    m_sPass = pass;
+}
+
+bool SMTPProtocol::sendCommandLine(const QByteArray &cmdline)
+{
+    //kDebug( cmdline.length() < 4096, 7112) << "C: " << cmdline.data();
+    //kDebug( cmdline.length() >= 4096, 7112) << "C: <" << cmdline.length() << " bytes>";
+    if (cmdline.length() < 4096) {
+        qCDebug(SMTP_LOG) << "C: >>" << cmdline.trimmed().data() << "<<";
+    } else {
+        qCDebug(SMTP_LOG) << "C: <" << cmdline.length() << " bytes>";
+    }
+    ssize_t numWritten, cmdline_len = cmdline.length();
+    if ((numWritten = write(cmdline.data(), cmdline_len)) != cmdline_len) {
+        qCDebug(SMTP_LOG) << "Tried to write " << cmdline_len << " bytes, but only "
+                          << numWritten << " were written!" << endl;
+        error(KIO::ERR_SLAVE_DEFINED, i18n("Writing to socket failed."));
+        return false;
+    }
+    return true;
+}
+
+Response SMTPProtocol::getResponse(bool *ok)
+{
+
+    if (ok) {
+        *ok = false;
+    }
+
+    Response response;
+    char buf[2048];
+
+    int recv_len = 0;
+    do {
+        // wait for data...
+        if (!waitForResponse(600)) {
+            error(KIO::ERR_SERVER_TIMEOUT, m_sServer);
+            return response;
+        }
+
+        // ...read data...
+        recv_len = readLine(buf, sizeof(buf) - 1);
+        if (recv_len < 1 && !isConnected()) {
+            error(KIO::ERR_CONNECTION_BROKEN, m_sServer);
+            return response;
+        }
+
+        qCDebug(SMTP_LOG) << "S: >>" << QByteArray(buf, recv_len).trimmed().data() << "<<";
+        // ...and parse lines...
+        response.parseLine(buf, recv_len);
+
+        // ...until the response is complete or the parser is so confused
+        // that it doesn't think a RSET would help anymore:
+    } while (!response.isComplete() && response.isWellFormed());
+
+    if (!response.isValid()) {
+        error(KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response (%1) received.", response.code()));
+        return response;
+    }
+
+    if (ok) {
+        *ok = true;
+    }
+
+    return response;
+}
+
+bool SMTPProtocol::executeQueuedCommands(TransactionState *ts)
+{
+    assert(ts);
+
+    if (m_sessionIface->canPipelineCommands()) {
+        qDebug() << "using pipelining";
+    }
+
+    while (!mPendingCommandQueue.isEmpty()) {
+        QByteArray cmdline = collectPipelineCommands(ts);
+        if (ts->failedFatally()) {
+            smtp_close(false);   // _hard_ shutdown
+            return false;
+        }
+        if (ts->failed()) {
+            break;
+        }
+        if (cmdline.isEmpty()) {
+            continue;
+        }
+        if (!sendCommandLine(cmdline) ||
+                !batchProcessResponses(ts) ||
+                ts->failedFatally()) {
+            smtp_close(false);   // _hard_ shutdown
+            return false;
+        }
+    }
+
+    if (ts->failed()) {
+        if (!execute(Command::RSET)) {
+            smtp_close(false);
+        }
+        return false;
+    }
+    return true;
+}
+
+QByteArray SMTPProtocol::collectPipelineCommands(TransactionState *ts)
+{
+    assert(ts);
+
+    QByteArray cmdLine;
+    unsigned int cmdLine_len = 0;
+
+    while (!mPendingCommandQueue.isEmpty()) {
+
+        Command *cmd = mPendingCommandQueue.head();
+
+        if (cmd->doNotExecute(ts)) {
+            delete mPendingCommandQueue.dequeue();
+            if (cmdLine_len) {
+                break;
+            } else {
+                continue;
+            }
+        }
+
+        if (cmdLine_len && cmd->mustBeFirstInPipeline()) {
+            break;
+        }
+
+        if (cmdLine_len && !m_sessionIface->canPipelineCommands()) {
+            break;
+        }
+
+        while (!cmd->isComplete() && !cmd->needsResponse()) {
+            const QByteArray currentCmdLine = cmd->nextCommandLine(ts);
+            if (ts->failedFatally()) {
+                return cmdLine;
+            }
+            const unsigned int currentCmdLine_len = currentCmdLine.length();
+
+            cmdLine_len += currentCmdLine_len;
+            cmdLine += currentCmdLine;
+
+            // If we are executing the transfer command, don't collect the whole
+            // command line (which may be several MBs) before sending it, but instead
+            // send the data each time we have collected 32 KB of the command line.
+            //
+            // This way, the progress information in clients like KMail works correctly,
+            // because otherwise, the TransferCommand would read the whole data from the
+            // job at once, then sending it. The progress update on the client however
+            // happens when sending data to the job, not when this slave writes the data
+            // to the socket. Therefore that progress update is incorrect.
+            //
+            // 32 KB seems to be a sensible limit. Additionally, a job can only transfer
+            // 32 KB at once anyway.
+            if (dynamic_cast<TransferCommand *>(cmd) != 0 &&
+                    cmdLine_len >= 32 * 1024) {
+                return cmdLine;
+            }
+        }
+
+        mSentCommandQueue.enqueue(mPendingCommandQueue.dequeue());
+
+        if (cmd->mustBeLastInPipeline()) {
+            break;
+        }
+    }
+
+    return cmdLine;
+}
+
+bool SMTPProtocol::batchProcessResponses(TransactionState *ts)
+{
+    assert(ts);
+
+    while (!mSentCommandQueue.isEmpty()) {
+
+        Command *cmd = mSentCommandQueue.head();
+        assert(cmd->isComplete());
+
+        bool ok = false;
+        Response r = getResponse(&ok);
+        if (!ok) {
+            return false;
+        }
+        cmd->processResponse(r, ts);
+        if (ts->failedFatally()) {
+            return false;
+        }
+
+        delete mSentCommandQueue.dequeue();
+    }
+
+    return true;
+}
+
+void SMTPProtocol::queueCommand(int type)
+{
+    queueCommand(Command::createSimpleCommand(type, m_sessionIface));
+}
+
+bool SMTPProtocol::execute(int type, TransactionState *ts)
+{
+    unique_ptr<Command> cmd(Command::createSimpleCommand(type, m_sessionIface));
+    if (!cmd.get()) {
+        qCritical() << "Command::createSimpleCommand( " << type << " ) returned null!";
+    }
+    return execute(cmd.get(), ts);
+}
+
+// ### fold into pipelining engine? How? (execute() is often called
+// ### when command queues are _not_ empty!)
+bool SMTPProtocol::execute(Command *cmd, TransactionState *ts)
+{
+
+    if (!cmd) {
+        qCritical() << "SMTPProtocol::execute() called with no command to run!";
+    }
+
+    if (cmd->doNotExecute(ts)) {
+        return true;
+    }
+
+    do {
+        while (!cmd->isComplete() && !cmd->needsResponse()) {
+            const QByteArray cmdLine = cmd->nextCommandLine(ts);
+            if (ts && ts->failedFatally()) {
+                smtp_close(false);
+                return false;
+            }
+            if (cmdLine.isEmpty()) {
+                continue;
+            }
+            if (!sendCommandLine(cmdLine)) {
+                smtp_close(false);
+                return false;
+            }
+        }
+
+        bool ok = false;
+        Response r = getResponse(&ok);
+        if (!ok) {
+            // Only close without sending QUIT if the responce was incomplete
+            // rfc5321 forbidds a client from closing a connection without sending
+            // QUIT (section 4.1.1.10)
+            if (r.isComplete()) {
+                smtp_close();
+            } else {
+                smtp_close(false);
+            }
+            return false;
+        }
+        if (!cmd->processResponse(r, ts)) {
+            if ((ts && ts->failedFatally()) ||
+                    cmd->closeConnectionOnError() ||
+                    !execute(Command::RSET)) {
+                smtp_close(false);
+            }
+            return false;
+        }
+    } while (!cmd->isComplete());
+
+    return true;
+}
+
+bool SMTPProtocol::smtp_open(const QString &fakeHostname)
+{
+    if (m_opened &&
+            m_sOldPort == m_port &&
+            m_sOldServer == m_sServer &&
+            m_sOldUser == m_sUser &&
+            (fakeHostname.isNull() || m_hostname == fakeHostname)) {
+        return true;
+    }
+
+    smtp_close();
+    if (!connectToHost(isAutoSsl() ? QStringLiteral("smtps") : QStringLiteral("smtp"), m_sServer, m_port)) {
+        return false;    // connectToHost has already send an error message.
+    }
+    m_opened = true;
+
+    bool ok = false;
+    Response greeting = getResponse(&ok);
+    if (!ok || !greeting.isOk()) {
+        if (ok) {
+            error(KIO::ERR_COULD_NOT_LOGIN,
+                  i18n("The server (%1) did not accept the connection.\n"
+                       "%2", m_sServer,  greeting.errorMessage()));
+        }
+        smtp_close();
+        return false;
+    }
+
+    if (!fakeHostname.isNull()) {
+        m_hostname = fakeHostname;
+    } else {
+        // FIXME: We need a way to find the FQDN again. Also change in servertest then.
+        m_hostname = QHostInfo::localHostName();
+        if (m_hostname.isEmpty()) {
+            m_hostname = QStringLiteral("localhost.invalid");
+        } else if (!m_hostname.contains(QLatin1Char('.'))) {
+            m_hostname += QLatin1String(".localnet");
+        }
+    }
+
+    EHLOCommand ehloCmdPreTLS(m_sessionIface, m_hostname);
+    if (!execute(&ehloCmdPreTLS)) {
+        smtp_close();
+        return false;
+    }
+
+    if ((m_sessionIface->haveCapability("STARTTLS") /*### && canUseTLS()*/ && m_sessionIface->tlsRequested() != SMTPSessionInterface::ForceNoTLS)
+            || m_sessionIface->tlsRequested() == SMTPSessionInterface::ForceTLS) {
+        // For now we're gonna force it on.
+
+        if (execute(Command::STARTTLS)) {
+
+            // re-issue EHLO to refresh the capability list (could be have
+            // been faked before TLS was enabled):
+            EHLOCommand ehloCmdPostTLS(m_sessionIface, m_hostname);
+            if (!execute(&ehloCmdPostTLS)) {
+                smtp_close();
+                return false;
+            }
+        }
+    }
+    // Now we try and login
+    if (!authenticate()) {
+        smtp_close();
+        return false;
+    }
+
+    m_sOldPort = m_port;
+    m_sOldServer = m_sServer;
+    m_sOldUser = m_sUser;
+    m_sOldPass = m_sPass;
+
+    return true;
+}
+
+bool SMTPProtocol::authenticate()
+{
+    // return with success if the server doesn't support SMTP-AUTH or an user
+    // name is not specified and metadata doesn't tell us to force it.
+    if ((m_sUser.isEmpty() || !m_sessionIface->haveCapability("AUTH")) &&
+            m_sessionIface->requestedSaslMethod().isEmpty()) {
+        return true;
+    }
+
+    KIO::AuthInfo authInfo;
+    authInfo.username = m_sUser;
+    authInfo.password = m_sPass;
+    authInfo.prompt = i18n("Username and password for your SMTP account:");
+
+    QStringList strList;
+
+    if (!m_sessionIface->requestedSaslMethod().isEmpty()) {
+        strList.append(m_sessionIface->requestedSaslMethod());
+    } else {
+        strList = m_sessionIface->capabilities().saslMethodsQSL();
+    }
+
+    const QByteArray ba = strList.join(QStringLiteral(" ")).toLatin1();
+    AuthCommand authCmd(m_sessionIface, ba.constData(), m_sServer, authInfo);
+    bool ret = execute(&authCmd);
+    m_sUser = authInfo.username;
+    m_sPass = authInfo.password;
+    return ret;
+}
+
+void SMTPProtocol::smtp_close(bool nice)
+{
+    if (!m_opened) {                // We're already closed
+        return;
+    }
+
+    if (nice) {
+        execute(Command::QUIT);
+    }
+    qCDebug(SMTP_LOG) << "closing connection";
+    disconnectFromHost();
+    m_sOldServer.clear();
+    m_sOldUser.clear();
+    m_sOldPass.clear();
+
+    m_sessionIface->clearCapabilities();
+    qDeleteAll(mPendingCommandQueue);
+    mPendingCommandQueue.clear();
+    qDeleteAll(mSentCommandQueue);
+    mSentCommandQueue.clear();
+
+    m_opened = false;
+}
+
+void SMTPProtocol::stat(const QUrl &url)
+{
+    QString path = url.path();
+    error(KIO::ERR_DOES_NOT_EXIST, url.path());
+}
diff --git a/kioslave/src/smtp/smtp.h b/kioslave/src/smtp/smtp.h
new file mode 100644
index 0000000..febc3ab
--- /dev/null
+++ b/kioslave/src/smtp/smtp.h
@@ -0,0 +1,126 @@
+/* -*- c++ -*-
+ * Copyright (c) 2000, 2001 Alex Zepeda <zipzippy at sonic.net>
+ * Copyright (c) 2001 Michael H�kel <Michael at Haeckel.Net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _SMTP_H
+#define _SMTP_H
+
+#include <kio/tcpslavebase.h>
+
+#include "capabilities.h"
+
+#include <QQueue>
+#include <QByteArray>
+
+class QUrl;
+
+namespace KioSMTP
+{
+class Response;
+class TransactionState;
+class Command;
+class SMTPSessionInterface;
+class KioSlaveSession;
+}
+
+class SMTPProtocol : public KIO::TCPSlaveBase
+{
+    friend class KioSMTP::KioSlaveSession;
+public:
+    SMTPProtocol(const QByteArray &pool, const QByteArray &app, bool useSSL);
+    virtual ~ SMTPProtocol();
+
+    virtual void setHost(const QString &host, quint16 port,
+                         const QString &user, const QString &pass) Q_DECL_OVERRIDE;
+
+    void special(const QByteArray &aData) Q_DECL_OVERRIDE;
+    void put(const QUrl &url, int permissions, KIO::JobFlags flags) Q_DECL_OVERRIDE;
+    void stat(const QUrl &url) Q_DECL_OVERRIDE;
+    void openConnection() Q_DECL_OVERRIDE;
+    void closeConnection() Q_DECL_OVERRIDE;
+
+protected:
+
+    bool smtp_open(const QString &fakeHostName);
+
+    /** Closes the connection. If @p nice is true (default), then QUIT
+        is sent and it's reponse waited for. */
+    void smtp_close(bool nice = true);
+
+    /** Execute command @p cmd */
+    bool execute(KioSMTP::Command *cmd, KioSMTP::TransactionState *ts = 0);
+    /** Execute a command of type @p type */
+    bool execute(int type, KioSMTP::TransactionState *ts = 0);
+    /** Execute the queued commands. If something goes horribly wrong
+        (sending command oline fails, getting response fails or some
+        command raises the failedFatally() flag in @p ts, shuts down the
+        connection with <code>smtp_close( false )</code>. If The
+        transaction fails gracefully (<code>ts->failed()</code> is
+        true), issues a RSET command.
+
+        @return true if transaction succeeded, false otherwise.
+    **/
+    bool executeQueuedCommands(KioSMTP::TransactionState *ts);
+
+    /** Parse a single response from the server. Single- vs. multiline
+        responses are correctly detected.
+
+        @param ok if not 0, returns whether response parsing was
+                  successful. Don't confuse this with negative responses
+                  (e.g. 5xx), which you can check for using
+                  @ref Response::isNegative()
+        @return the @ref Response object representing the server response.
+    **/
+    KioSMTP::Response getResponse(bool *ok);
+
+    bool authenticate();
+
+    bool sendCommandLine(const QByteArray &cmd);
+    QByteArray collectPipelineCommands(KioSMTP::TransactionState *ts);
+    bool batchProcessResponses(KioSMTP::TransactionState *ts);
+
+    void queueCommand(KioSMTP::Command *command)
+    {
+        mPendingCommandQueue.enqueue(command);
+    }
+    void queueCommand(int type);
+
+    quint16  m_sOldPort;
+    quint16 m_port;
+    bool m_opened;
+    QString m_sServer, m_sOldServer;
+    QString m_sUser, m_sOldUser;
+    QString m_sPass, m_sOldPass;
+    QString m_hostname;
+
+    typedef QQueue<KioSMTP::Command *> CommandQueue;
+    CommandQueue mPendingCommandQueue;
+    CommandQueue mSentCommandQueue;
+    KioSMTP::SMTPSessionInterface *m_sessionIface;
+};
+
+#endif // _SMTP_H
diff --git a/kioslave/src/smtp/smtp.protocol b/kioslave/src/smtp/smtp.protocol
new file mode 100644
index 0000000..c7bf05b
--- /dev/null
+++ b/kioslave/src/smtp/smtp.protocol
@@ -0,0 +1,16 @@
+[Protocol]
+exec=kf5/kio/smtp
+protocol=smtp
+Capabilities=SASL
+input=none
+output=filesystem
+listing=Name,Type,Size
+reading=false
+writing=true
+deleting=false
+source=true
+makedir=false
+linking=false
+moving=false
+X-DocPath=kioslave5/smtp/index.html
+Icon=mail-folder-outbox
diff --git a/kioslave/src/smtp/smtps.protocol b/kioslave/src/smtp/smtps.protocol
new file mode 100644
index 0000000..98222d7
--- /dev/null
+++ b/kioslave/src/smtp/smtps.protocol
@@ -0,0 +1,16 @@
+[Protocol]
+exec=kf5/kio/smtp
+protocol=smtps
+Capabilities=SASL
+input=none
+output=filesystem
+listing=Name,Type,Size
+reading=false
+writing=true
+deleting=false
+source=true
+makedir=false
+linking=false
+moving=false
+X-DocPath=kioslave5/smtp/index.html
+Icon=mail-folder-outbox
diff --git a/kioslave/src/smtp/smtpsessioninterface.cpp b/kioslave/src/smtp/smtpsessioninterface.cpp
new file mode 100644
index 0000000..b0e33df
--- /dev/null
+++ b/kioslave/src/smtp/smtpsessioninterface.cpp
@@ -0,0 +1,61 @@
+/*
+    Copyright (c) 2010 Volker Krause <vkrause at 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 "smtpsessioninterface.h"
+
+using namespace KioSMTP;
+
+SMTPSessionInterface::~SMTPSessionInterface()
+{
+}
+
+void SMTPSessionInterface::parseFeatures(const KioSMTP::Response &ehloResponse)
+{
+    m_capabilities = Capabilities::fromResponse(ehloResponse);
+}
+
+const Capabilities &KioSMTP::SMTPSessionInterface::capabilities() const
+{
+    return m_capabilities;
+}
+
+void SMTPSessionInterface::clearCapabilities()
+{
+    m_capabilities.clear();
+}
+
+bool SMTPSessionInterface::haveCapability(const char *cap) const
+{
+    return m_capabilities.have(cap);
+}
+
+bool SMTPSessionInterface::canPipelineCommands() const
+{
+    return haveCapability("PIPELINING") && pipeliningRequested();
+}
+
+bool KioSMTP::SMTPSessionInterface::eightBitMimeRequested() const
+{
+    return false;
+}
+
+bool KioSMTP::SMTPSessionInterface::pipeliningRequested() const
+{
+    return true;
+}
diff --git a/kioslave/src/smtp/smtpsessioninterface.h b/kioslave/src/smtp/smtpsessioninterface.h
new file mode 100644
index 0000000..0bb7b4f
--- /dev/null
+++ b/kioslave/src/smtp/smtpsessioninterface.h
@@ -0,0 +1,96 @@
+/*
+    Copyright (c) 2010 Volker Krause <vkrause at 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 KIOSMTP_SMTPSESSIONINTERFACE_H
+#define KIOSMTP_SMTPSESSIONINTERFACE_H
+
+#include "capabilities.h"
+
+class QByteArray;
+class QString;
+
+namespace KIO
+{
+class AuthInfo;
+}
+
+namespace KioSMTP
+{
+
+class Response;
+
+/** Interface to the SMTP session for command classes.
+ * There are sub-classes for the in-process mode, the KIO slave mode and for unit testing.
+ * @since 4.6
+ */
+class SMTPSessionInterface
+{
+public:
+    /** TLS request state. */
+    enum TLSRequestState {
+        UseTLSIfAvailable,
+        ForceTLS,
+        ForceNoTLS
+    };
+
+    virtual ~SMTPSessionInterface();
+    virtual bool startSsl() = 0;
+
+    /** Parse capability response from the server. */
+    void parseFeatures(const KioSMTP::Response &ehloResponse);
+
+    /** Returns the server reported capabilities. */
+    const Capabilities &capabilities() const;
+
+    /** Clear the capabilities reported by the server (e.g. when reconnecting the session) */
+    void clearCapabilities();
+
+    /** This is a pure convenience wrapper around
+     *  @ref KioSMTP::Capabilities::have()
+     */
+    virtual bool haveCapability(const char *cap) const;
+
+    /** @return true is pipelining is available and allowed by metadata */
+    bool canPipelineCommands() const;
+
+    virtual void error(int id, const QString &msg) = 0;
+    /** Show information message box with message @p msg and caption @p caption. */
+    virtual void informationMessageBox(const QString &msg, const QString &caption) = 0;
+    virtual bool openPasswordDialog(KIO::AuthInfo &authInfo) = 0;
+    virtual void dataReq() = 0;
+    virtual int readData(QByteArray &ba) = 0;
+
+    /** SASL method requested for authentication. */
+    virtual QString requestedSaslMethod() const = 0;
+    /** TLS requested for encryption. */
+    virtual TLSRequestState tlsRequested() const = 0;
+    /** LF2CRLF and dot stuffing requested. */
+    virtual bool lf2crlfAndDotStuffingRequested() const = 0;
+    /** 8bit MIME support requested. */
+    virtual bool eightBitMimeRequested() const;
+    /** Pipelining has been requested. */
+    virtual bool pipeliningRequested() const;
+
+private :
+    KioSMTP::Capabilities m_capabilities;
+};
+
+}
+
+#endif
diff --git a/kioslave/src/smtp/tests/CMakeLists.txt b/kioslave/src/smtp/tests/CMakeLists.txt
new file mode 100644
index 0000000..e97ee9b
--- /dev/null
+++ b/kioslave/src/smtp/tests/CMakeLists.txt
@@ -0,0 +1,67 @@
+include(ECMMarkAsTest)
+
+set(QT_REQUIRED_VERSION "5.4.0")
+find_package(Qt5Test ${QT_REQUIRED_VERSION} CONFIG REQUIRED)
+
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+
+########### next target ###############
+
+set(test_responseparser_SRCS test_responseparser.cpp )
+
+add_executable( test_responseparser ${test_responseparser_SRCS} )
+add_test( test_responseparser test_responseparser )
+ecm_mark_as_test(smtp-responseparser)
+target_link_libraries(test_responseparser   Qt5::Test KF5::I18n KF5::KIOCore)
+
+########### next target ###############
+
+set(test_headergeneration_SRCS test_headergeneration.cpp)
+ecm_qt_declare_logging_category(test_headergeneration_SRCS HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME log_smtp)
+
+add_executable( test_headergeneration  ${test_headergeneration_SRCS} )
+add_test( test_headergeneration test_headergeneration )
+ecm_mark_as_test(smtp-headergeneration)
+
+target_link_libraries(test_headergeneration  Qt5::Test )
+
+
+########### next target ###############
+set(test_commands_SRCS test_commands.cpp )
+ecm_qt_declare_logging_category(test_commands_SRCS HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME log_smtp)
+
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${Sasl2_INCLUDE_DIRS}  ${kioslave_SOURCE_DIR}/src/)
+
+add_executable( test_commands ${test_commands_SRCS} )
+add_test( test_commands test_commands )
+ecm_mark_as_test(smtp-commands)
+target_link_libraries(test_commands   KF5::KIOCore ${Sasl2_LIBRARIES} Qt5::Test KF5::I18n)
+
+
+########### next target ###############
+set(interactivesmtpserver_SRCS interactivesmtpserver.cpp )
+
+add_executable( interactivesmtpserver ${interactivesmtpserver_SRCS} )
+ecm_mark_as_test(smtp-interactivesmtpserver)
+target_link_libraries(interactivesmtpserver    Qt5::Test Qt5::Widgets Qt5::Network)
+
+
+########### next target ###############
+set(test_capabilities_SRCS test_capabilities.cpp ../capabilities.cpp )
+
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${kioslave_SOURCE_DIR}/src/smtp)
+
+add_executable( test_capabilities ${test_capabilities_SRCS} )
+ecm_mark_as_test(test-capabilities)
+target_link_libraries(test_capabilities   KF5::KIOCore)
+
+
+########### next target ###############
+set( test_request_source requesttest.cpp ../request.cpp )
+ecm_qt_declare_logging_category(test_request_source HEADER smtp_debug.h IDENTIFIER SMTP_LOG CATEGORY_NAME log_smtp)
+
+add_executable( requesttest ${test_request_source})
+add_test(requesttest requesttest)
+ecm_mark_as_test(requesttest)
+target_link_libraries( requesttest Qt5::Test Qt5::Gui)
+
diff --git a/kioslave/src/smtp/tests/fakesession.h b/kioslave/src/smtp/tests/fakesession.h
new file mode 100644
index 0000000..2b23f12
--- /dev/null
+++ b/kioslave/src/smtp/tests/fakesession.h
@@ -0,0 +1,120 @@
+/*
+    Copyright (c) 2010 Volker Krause <vkrause at 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 KIOSMTP_FAKESESSION_H
+#define KIOSMTP_FAKESESSION_H
+
+#include "smtpsessioninterface.h"
+
+#include <QStringList>
+#include <kio/slavebase.h>
+
+namespace KioSMTP
+{
+
+class FakeSession : public SMTPSessionInterface
+{
+public:
+    FakeSession()
+    {
+        clear();
+    }
+
+    //
+    // public members to control the API emulation below:
+    //
+    bool startTLSReturnCode;
+    bool usesTLS; // ### unused below, most likely something wrong in the tests...
+    int lastErrorCode;
+    QString lastErrorMessage;
+    QString lastMessageBoxText;
+    QByteArray nextData;
+    int nextDataReturnCode;
+    QStringList caps;
+
+    bool eightBitMime;
+    bool lf2crlfAndDotStuff;
+    bool pipelining;
+    QString saslMethod;
+
+    void clear()
+    {
+        startTLSReturnCode = true;
+        usesTLS = false;
+        lastErrorCode = 0;
+        lastErrorMessage.clear();
+        lastMessageBoxText.clear();
+        nextData.resize(0);
+        nextDataReturnCode = -1;
+        caps.clear();
+
+        lf2crlfAndDotStuff = false;
+        saslMethod.clear();
+    }
+
+    //
+    // emulated API:
+    //
+    bool startSsl() Q_DECL_OVERRIDE {
+        return startTLSReturnCode;
+    }
+    bool haveCapability(const char *cap) const Q_DECL_OVERRIDE
+    {
+        return caps.contains(QLatin1String(cap));
+    }
+    void error(int id, const QString &msg) Q_DECL_OVERRIDE {
+        lastErrorCode = id;
+        lastErrorMessage = msg;
+        qWarning() << id << msg;
+    }
+    void informationMessageBox(const QString &msg, const QString &caption) Q_DECL_OVERRIDE {
+        Q_UNUSED(caption);
+        lastMessageBoxText = msg;
+    }
+    bool openPasswordDialog(KIO::AuthInfo &) Q_DECL_OVERRIDE {
+        return true;
+    }
+    void dataReq() Q_DECL_OVERRIDE {
+        /* noop */
+    }
+    int readData(QByteArray &ba) Q_DECL_OVERRIDE {
+        ba = nextData;
+        return nextDataReturnCode;
+    }
+
+    bool lf2crlfAndDotStuffingRequested() const Q_DECL_OVERRIDE
+    {
+        return lf2crlfAndDotStuff;
+    }
+    QString requestedSaslMethod() const Q_DECL_OVERRIDE
+    {
+        return saslMethod;
+    }
+    TLSRequestState tlsRequested() const Q_DECL_OVERRIDE
+    {
+        return SMTPSessionInterface::UseTLSIfAvailable;
+    }
+};
+
+}
+
+#include "smtpsessioninterface.cpp"
+#include "capabilities.cpp"
+
+#endif
diff --git a/kioslave/src/smtp/tests/interactivesmtpserver.cpp b/kioslave/src/smtp/tests/interactivesmtpserver.cpp
new file mode 100644
index 0000000..95cf711
--- /dev/null
+++ b/kioslave/src/smtp/tests/interactivesmtpserver.cpp
@@ -0,0 +1,206 @@
+/*  -*- c++ -*-
+    interactivesmtpserver.cc
+
+    Code based on the serverSocket example by Jesper Pedersen.
+
+    This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2004 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#include <QtCore/QString>
+#include <QtCore/QTextStream>
+#include <QApplication>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QTextEdit>
+#include <QWidget>
+#include <QTcpSocket>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+
+#include "interactivesmtpserver.h"
+
+static const QHostAddress localhost(0x7f000001);   // 127.0.0.1
+
+static QString err2str(QAbstractSocket::SocketError error)
+{
+    switch (error) {
+    case QAbstractSocket::ConnectionRefusedError: return QStringLiteral("Connection refused");
+    case QAbstractSocket::HostNotFoundError: return QStringLiteral("Host not found");
+    default: return QStringLiteral("Unknown error");
+    }
+}
+
+static QString escape(QString s)
+{
+    return s
+           .replace(QLatin1Char('&'), QLatin1String("&"))
+           .replace(QLatin1Char('>'), QLatin1String(">"))
+           .replace(QLatin1Char('<'), QLatin1String("<"))
+           .replace(QLatin1Char('"'), QLatin1String("""))
+           ;
+}
+
+static QString trim(const QString &s)
+{
+    if (s.endsWith(QLatin1String("\r\n"))) {
+        return s.left(s.length() - 2);
+    }
+    if (s.endsWith(QLatin1String("\r")) || s.endsWith(QLatin1String("\n"))) {
+        return s.left(s.length() - 1);
+    }
+    return s;
+}
+
+InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow()
+{
+    if (mSocket) {
+        mSocket->close();
+        if (mSocket->state() == QAbstractSocket::ClosingState)
+            connect(mSocket, SIGNAL(disconnected()),
+                    mSocket, SLOT(deleteLater()));
+        else {
+            mSocket->deleteLater();
+        }
+        mSocket = 0;
+    }
+}
+
+void InteractiveSMTPServerWindow::slotSendResponse()
+{
+    const QString line = mLineEdit->text();
+    mLineEdit->clear();
+    QTextStream s(mSocket);
+    s << line + QLatin1String("\r\n");
+    slotDisplayServer(line);
+}
+
+InteractiveSMTPServer::InteractiveSMTPServer(QObject *parent)
+    : QTcpServer(parent)
+{
+    listen(localhost, 2525);
+    setMaxPendingConnections(1);
+
+    connect(this, SIGNAL(newConnection()), this, SLOT(newConnectionAvailable()));
+}
+
+void InteractiveSMTPServer::newConnectionAvailable()
+{
+    InteractiveSMTPServerWindow *w = new InteractiveSMTPServerWindow(nextPendingConnection());
+    w->show();
+}
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+
+    InteractiveSMTPServer server;
+
+    qDebug("Server should now listen on localhost:2525");
+    qDebug("Hit CTRL-C to quit.");
+
+    return app.exec();
+}
+
+InteractiveSMTPServerWindow::InteractiveSMTPServerWindow(QTcpSocket *socket, QWidget *parent)
+    : QWidget(parent), mSocket(socket)
+{
+    QPushButton *but;
+    Q_ASSERT(socket);
+
+    QVBoxLayout *vlay = new QVBoxLayout(this);
+
+    mTextEdit = new QTextEdit(this);
+    vlay->addWidget(mTextEdit, 1);
+    QWidget *mLayoutWidget = new QWidget;
+    vlay->addWidget(mLayoutWidget);
+
+    QHBoxLayout *hlay = new QHBoxLayout(mLayoutWidget);
+
+    mLineEdit = new QLineEdit(this);
+    mLabel = new QLabel(QStringLiteral("&Response:"), this);
+    mLabel->setBuddy(mLineEdit);
+    but = new QPushButton(QStringLiteral("&Send"), this);
+    hlay->addWidget(mLabel);
+    hlay->addWidget(mLineEdit, 1);
+    hlay->addWidget(but);
+
+    connect(mLineEdit, SIGNAL(returnPressed()), SLOT(slotSendResponse()));
+    connect(but, SIGNAL(clicked()), SLOT(slotSendResponse()));
+
+    but = new QPushButton(QStringLiteral("&Close Connection"), this);
+    vlay->addWidget(but);
+
+    connect(but, SIGNAL(clicked()), SLOT(slotConnectionClosed()));
+
+    connect(socket, SIGNAL(disconnected()), SLOT(slotConnectionClosed()));
+    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
+            SLOT(slotError(QAbstractSocket::SocketError)));
+    connect(socket, SIGNAL(readyRead()), SLOT(slotReadyRead()));
+
+    mLineEdit->setText(QStringLiteral("220 hi there"));
+    mLineEdit->setFocus();
+}
+
+void InteractiveSMTPServerWindow::slotDisplayClient(const QString &s)
+{
+    mTextEdit->append(QLatin1String("C:") + escape(s));
+}
+
+void InteractiveSMTPServerWindow::slotDisplayServer(const QString &s)
+{
+    mTextEdit->append(QLatin1String("S:") + escape(s));
+}
+
+void InteractiveSMTPServerWindow::slotDisplayMeta(const QString &s)
+{
+    mTextEdit->append(QLatin1String("<font color=\"red\">") + escape(s) + QLatin1String("</font>"));
+}
+
+void InteractiveSMTPServerWindow::slotReadyRead()
+{
+    while (mSocket->canReadLine()) {
+        slotDisplayClient(trim(QString::fromLatin1(mSocket->readLine())));
+    }
+}
+
+void InteractiveSMTPServerWindow::slotError(QAbstractSocket::SocketError error)
+{
+    slotDisplayMeta(QString::fromLatin1("E: %1").arg(err2str(error)));
+}
+
+void InteractiveSMTPServerWindow::slotConnectionClosed()
+{
+    slotDisplayMeta(QStringLiteral("Connection closed by peer"));
+}
+
+void InteractiveSMTPServerWindow::slotCloseConnection()
+{
+    mSocket->close();
+}
+
diff --git a/kioslave/src/smtp/tests/interactivesmtpserver.h b/kioslave/src/smtp/tests/interactivesmtpserver.h
new file mode 100644
index 0000000..72d2676
--- /dev/null
+++ b/kioslave/src/smtp/tests/interactivesmtpserver.h
@@ -0,0 +1,81 @@
+#ifndef INTERACTIVESMTPSERVER_H
+#define INTERACTIVESMTPSERVER_H
+
+/*  -*- c++ -*-
+    interactivesmtpserver.h
+
+    Code based on the serverSocket example by Jesper Pedersen.
+
+    This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2004 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#include <QWidget>
+#include <QtNetwork/QTcpServer>
+
+class QLabel;
+class QLineEdit;
+class QTcpServer;
+class QTextEdit;
+
+class InteractiveSMTPServerWindow : public QWidget
+{
+    Q_OBJECT
+public:
+    InteractiveSMTPServerWindow(QTcpSocket *socket, QWidget *parent = Q_NULLPTR);
+    ~InteractiveSMTPServerWindow();
+
+public Q_SLOTS:
+    void slotSendResponse();
+    void slotDisplayClient(const QString &s);
+    void slotDisplayServer(const QString &s);
+    void slotDisplayMeta(const QString &s);
+    void slotReadyRead();
+    void slotError(QAbstractSocket::SocketError error);
+    void slotConnectionClosed();
+    void slotCloseConnection();
+
+private:
+    QTcpSocket *mSocket;
+    QTextEdit *mTextEdit;
+    QLineEdit *mLineEdit;
+    QLabel *mLabel;
+};
+
+class InteractiveSMTPServer : public QTcpServer
+{
+    Q_OBJECT
+
+public:
+    InteractiveSMTPServer(QObject *parent = Q_NULLPTR);
+    ~InteractiveSMTPServer() {}
+
+private Q_SLOTS:
+    void newConnectionAvailable();
+};
+
+#endif
diff --git a/kioslave/src/smtp/tests/requesttest.cpp b/kioslave/src/smtp/tests/requesttest.cpp
new file mode 100644
index 0000000..92025c8
--- /dev/null
+++ b/kioslave/src/smtp/tests/requesttest.cpp
@@ -0,0 +1,83 @@
+/*
+  Copyright (c) 2014 Montel Laurent <montel at kde.org>
+
+  This program is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License, version 2, as
+  published by the Free Software Foundation.
+
+  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 "requesttest.h"
+#include "../request.h"
+#include <qtest.h>
+#include <QUrl>
+RequestTest::RequestTest(QObject *parent)
+    : QObject(parent)
+{
+
+}
+
+RequestTest::~RequestTest()
+{
+
+}
+
+void RequestTest::shouldHaveDefaultValue()
+{
+    KioSMTP::Request request;
+    QVERIFY(request.to().isEmpty());
+    QVERIFY(request.cc().isEmpty());
+    QVERIFY(request.bcc().isEmpty());
+    QVERIFY(request.emitHeaders());
+    QVERIFY(!request.is8BitBody());
+    QVERIFY(request.profileName().isEmpty());
+    QVERIFY(request.fromAddress().isEmpty());
+    QVERIFY(request.heloHostname().isEmpty());
+    QCOMPARE(request.size(), static_cast<unsigned int>(0));
+}
+
+void RequestTest::shouldParseRequest_data()
+{
+    QTest::addColumn<QUrl>("smtpurl");
+    QTest::addColumn<QString>("to");
+    QTest::addColumn<QString>("from");
+    QTest::addColumn<QString>("cc");
+    QTest::addColumn<QString>("bcc");
+    QTest::addColumn<bool>("emitheaders");
+    QTest::addColumn<unsigned int>("size");
+    QTest::newRow("correct url") <<  QUrl(QStringLiteral("smtps://smtp.kde.org:465/send?headers=0&from=foo%40kde.org&to=foo%40kde.org&size=617"))
+                                 << QStringLiteral("foo at kde.org")
+                                 << QStringLiteral("foo at kde.org")
+                                 << QString()
+                                 << QString()
+                                 << false
+                                 << static_cast<unsigned int>(617);
+}
+
+void RequestTest::shouldParseRequest()
+{
+    QFETCH(QUrl, smtpurl);
+    QFETCH(QString, to);
+    QFETCH(QString, from);
+    QFETCH(QString, cc);
+    QFETCH(QString, bcc);
+    QFETCH(bool, emitheaders);
+    QFETCH(unsigned int, size);
+
+    KioSMTP::Request request = KioSMTP::Request::fromURL(smtpurl);
+    QCOMPARE(request.to().join(QLatin1String(",")), to);
+    QCOMPARE(request.cc().join(QLatin1String(",")), cc);
+    QCOMPARE(request.fromAddress(), from);
+    QCOMPARE(request.bcc().join(QLatin1String(",")), bcc);
+    QCOMPARE(request.size(), size);
+    QCOMPARE(request.emitHeaders(), emitheaders);
+}
+
+QTEST_MAIN(RequestTest)
diff --git a/kioslave/src/smtp/tests/requesttest.h b/kioslave/src/smtp/tests/requesttest.h
new file mode 100644
index 0000000..645d1bf
--- /dev/null
+++ b/kioslave/src/smtp/tests/requesttest.h
@@ -0,0 +1,35 @@
+/*
+  Copyright (c) 2014 Montel Laurent <montel at kde.org>
+
+  This program is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License, version 2, as
+  published by the Free Software Foundation.
+
+  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 REQUESTTEST_H
+#define REQUESTTEST_H
+
+#include <QObject>
+
+class RequestTest : public QObject
+{
+    Q_OBJECT
+public:
+    explicit RequestTest(QObject *parent = Q_NULLPTR);
+    ~RequestTest();
+private Q_SLOTS:
+    void shouldHaveDefaultValue();
+    void shouldParseRequest_data();
+    void shouldParseRequest();
+};
+
+#endif // REQUESTTEST_H
diff --git a/kioslave/src/smtp/tests/test_capabilities.cpp b/kioslave/src/smtp/tests/test_capabilities.cpp
new file mode 100644
index 0000000..f0a275c
--- /dev/null
+++ b/kioslave/src/smtp/tests/test_capabilities.cpp
@@ -0,0 +1,26 @@
+#include <assert.h>
+#include "capabilities.h"
+#include <QObject>
+
+using namespace KioSMTP;
+
+int main()
+{
+    Capabilities c;
+
+    const QString size_cap = QObject::tr("SIZE 12");
+    c.add(size_cap);
+    // Capability was added
+    assert(c.have("SIZE"));
+
+    const QString expected_response = QObject::tr("SIZE=12");
+    const QString actual_response = c.createSpecialResponse(false);
+    // SIZE actually handled
+    assert(actual_response == expected_response);
+
+    const QString auth_cap = QObject::tr("AUTH GSSAPI");
+    c.add(auth_cap);
+    c.add(auth_cap);
+    // Duplicate methods was removed
+    assert(c.saslMethodsQSL().length() == 1);
+}
diff --git a/kioslave/src/smtp/tests/test_commands.cpp b/kioslave/src/smtp/tests/test_commands.cpp
new file mode 100644
index 0000000..0338715
--- /dev/null
+++ b/kioslave/src/smtp/tests/test_commands.cpp
@@ -0,0 +1,677 @@
+#include <kio/global.h>
+#include <kio/authinfo.h>
+#include <qdebug.h>
+
+#define KIOSMTP_COMPARATORS // for TransactionState::operator==
+#include "fakesession.h"
+#include "command.h"
+#include "response.h"
+#include "transactionstate.h"
+#include "common.h"
+#include "smtp_debug.h"
+#include <assert.h>
+
+using namespace KioSMTP;
+
+static const char *foobarbaz = ".Foo bar baz";
+static const unsigned int foobarbaz_len = qstrlen(foobarbaz);
+
+static const char *foobarbaz_dotstuffed = "..Foo bar baz";
+static const unsigned int foobarbaz_dotstuffed_len = qstrlen(foobarbaz_dotstuffed);
+
+static const char *foobarbaz_lf = ".Foo bar baz\n";
+static const unsigned int foobarbaz_lf_len = qstrlen(foobarbaz_lf);
+
+static const char *foobarbaz_crlf = "..Foo bar baz\r\n";
+static const unsigned int foobarbaz_crlf_len = qstrlen(foobarbaz_crlf);
+
+static void checkSuccessfulTransferCommand(bool, bool, bool, bool, bool);
+
+int main(int, char **)
+{
+
+    if (!initSASL()) {
+        exit(-1);
+    }
+
+    FakeSession smtp;
+    Response r;
+    TransactionState ts, ts2;
+
+    //
+    // EHLO / HELO
+    //
+
+    smtp.clear();
+    EHLOCommand ehlo(&smtp, QStringLiteral("mail.example.com"));
+    // flags
+    assert(ehlo.closeConnectionOnError());
+    assert(ehlo.mustBeLastInPipeline());
+    assert(!ehlo.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!ehlo.isComplete());
+    assert(!ehlo.doNotExecute(0));
+    assert(!ehlo.needsResponse());
+
+    // dynamics 1: EHLO succeeds
+    assert(ehlo.nextCommandLine(0) == "EHLO mail.example.com\r\n");
+    assert(!ehlo.isComplete());   // EHLO may fail and we then try HELO
+    assert(ehlo.needsResponse());
+    r.clear();
+    r.parseLine("250-mail.example.net\r\n");
+    r.parseLine("250-PIPELINING\r\n");
+    r.parseLine("250 8BITMIME\r\n");
+    assert(ehlo.processResponse(r, 0) == true);
+    assert(ehlo.isComplete());
+    assert(!ehlo.needsResponse());
+    assert(smtp.lastErrorCode == 0);
+    assert(smtp.lastErrorMessage.isNull());
+
+    // dynamics 2: EHLO fails with "unknown command"
+    smtp.clear();
+    EHLOCommand ehlo2(&smtp, QStringLiteral("mail.example.com"));
+    ehlo2.nextCommandLine(0);
+    r.clear();
+    r.parseLine("500 unknown command\r\n");
+    assert(ehlo2.processResponse(r, 0) == true);
+    assert(!ehlo2.isComplete());
+    assert(!ehlo2.needsResponse());
+    assert(ehlo2.nextCommandLine(0) == "HELO mail.example.com\r\n");
+    assert(ehlo2.isComplete());
+    assert(ehlo2.needsResponse());
+    r.clear();
+    r.parseLine("250 mail.example.net\r\n");
+    assert(ehlo2.processResponse(r, 0) == true);
+    assert(!ehlo2.needsResponse());
+    assert(smtp.lastErrorCode == 0);
+    assert(smtp.lastErrorMessage.isNull());
+
+    // dynamics 3: EHLO fails with unknown response code
+    smtp.clear();
+    EHLOCommand ehlo3(&smtp, QStringLiteral("mail.example.com"));
+    ehlo3.nextCommandLine(0);
+    r.clear();
+    r.parseLine("545 you don't know me\r\n");
+    assert(ehlo3.processResponse(r, 0) == false);
+    assert(ehlo3.isComplete());
+    assert(!ehlo3.needsResponse());
+    assert(smtp.lastErrorCode == KIO::ERR_UNKNOWN);
+
+    // dynamics 4: EHLO _and_ HELO fail with "command unknown"
+    smtp.clear();
+    EHLOCommand ehlo4(&smtp, QStringLiteral("mail.example.com"));
+    ehlo4.nextCommandLine(0);
+    r.clear();
+    r.parseLine("500 unknown command\r\n");
+    ehlo4.processResponse(r, 0);
+    ehlo4.nextCommandLine(0);
+    r.clear();
+    r.parseLine("500 unknown command\r\n");
+    assert(ehlo4.processResponse(r, 0) == false);
+    assert(ehlo4.isComplete());
+    assert(!ehlo4.needsResponse());
+    assert(smtp.lastErrorCode == KIO::ERR_INTERNAL_SERVER);
+
+    //
+    // STARTTLS
+    //
+
+    smtp.clear();
+    StartTLSCommand tls(&smtp);
+    // flags
+    assert(tls.closeConnectionOnError());
+    assert(tls.mustBeLastInPipeline());
+    assert(!tls.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!tls.isComplete());
+    assert(!tls.doNotExecute(0));
+    assert(!tls.needsResponse());
+
+    // dynamics 1: ok from server, TLS negotiation successful
+    ts.clear();
+    ts2 = ts;
+    assert(tls.nextCommandLine(&ts) == "STARTTLS\r\n");
+    assert(ts == ts2);
+    assert(tls.isComplete());
+    assert(tls.needsResponse());
+    r.clear();
+    r.parseLine("220 Go ahead");
+    smtp.startTLSReturnCode = true;
+    assert(tls.processResponse(r, &ts) == true);
+    assert(!tls.needsResponse());
+    assert(smtp.lastErrorCode == 0);
+
+    // dynamics 2: NAK from server
+    smtp.clear();
+    StartTLSCommand tls2(&smtp);
+    ts.clear();
+    tls2.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("454 TLS temporarily disabled");
+    smtp.startTLSReturnCode = true;
+    assert(tls2.processResponse(r, &ts) == false);
+    assert(!tls2.needsResponse());
+    assert(smtp.lastErrorCode == KIO::ERR_SERVICE_NOT_AVAILABLE);
+
+    // dynamics 3: ok from server, TLS negotiation unsuccessful
+    smtp.clear();
+    StartTLSCommand tls3(&smtp);
+    ts.clear();
+    tls3.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("220 Go ahead");
+    smtp.startTLSReturnCode = false;
+    assert(tls.processResponse(r, &ts) == false);
+    assert(!tls.needsResponse());
+
+    //
+    // AUTH
+    //
+
+    smtp.clear();
+    QStringList mechs;
+    mechs.append(QStringLiteral("PLAIN"));
+    smtp.saslMethod = QStringLiteral("PLAIN");
+    KIO::AuthInfo authInfo;
+    authInfo.username = QStringLiteral("user");
+    authInfo.password = QStringLiteral("pass");
+    AuthCommand auth(&smtp, "PLAIN", QStringLiteral("mail.example.com"), authInfo);
+    // flags
+    assert(auth.closeConnectionOnError());
+    assert(auth.mustBeLastInPipeline());
+    assert(!auth.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!auth.isComplete());
+    assert(!auth.doNotExecute(0));
+    assert(!auth.needsResponse());
+
+    // dynamics 1: TLS, so AUTH should include initial-response:
+    smtp.usesTLS = true;
+    ts.clear();
+    ts2 = ts;
+    assert(auth.nextCommandLine(&ts) == "AUTH PLAIN dXNlcgB1c2VyAHBhc3M=\r\n");
+    assert(auth.isComplete());
+    assert(auth.needsResponse());
+    assert(ts == ts2);
+    r.clear();
+    r.parseLine("250 OK");
+
+    // dynamics 2: No TLS, so AUTH should not include initial-response:
+    /* FIXME fails since nothing evaluates useTLS = false anywhere...
+    smtp.clear();
+    smtp.saslMethod = "PLAIN";
+    smtp.usesTLS = false;
+    authInfo = KIO::AuthInfo();
+    authInfo.username = "user";
+    authInfo.password = "pass";
+    AuthCommand auth2( &smtp, "PLAIN", "mail.example.com", authInfo );
+    ts.clear();
+    assert( auth2.nextCommandLine( &ts ) == "AUTH PLAIN\r\n" );
+    assert( !auth2.isComplete() );
+    assert( auth2.needsResponse() );
+    r.clear();
+    r.parseLine( "334 Go on" );
+    assert( auth2.processResponse( r, &ts ) == true );
+    assert( auth2.nextCommandLine( &ts ) == "dXNlcgB1c2VyAHBhc3M=\r\n" );
+    assert( auth2.isComplete() );
+    assert( auth2.needsResponse() );*/
+
+    // dynamics 3: LOGIN
+    smtp.clear();
+    smtp.saslMethod = QStringLiteral("LOGIN");
+    mechs.clear();
+    mechs.append(QStringLiteral("LOGIN"));
+    authInfo = KIO::AuthInfo();
+    authInfo.username = QStringLiteral("user");
+    authInfo.password = QStringLiteral("pass");
+    AuthCommand auth3(&smtp, "LOGIN", QStringLiteral("mail.example.com"), authInfo);
+    ts.clear();
+    ts2 = ts;
+    assert(auth3.nextCommandLine(&ts) == "AUTH LOGIN\r\n");
+    assert(!auth3.isComplete());
+    assert(auth3.needsResponse());
+    r.clear();
+    r.parseLine("334 VXNlcm5hbWU6");
+    assert(auth3.processResponse(r, &ts) == true);
+    assert(!auth3.needsResponse());
+    assert(auth3.nextCommandLine(&ts) == "dXNlcg==\r\n");
+    assert(!auth3.isComplete());
+    assert(auth3.needsResponse());
+    r.clear();
+    r.parseLine("334 go on");
+    assert(auth3.processResponse(r, &ts) == true);
+    assert(!auth3.needsResponse());
+    assert(auth3.nextCommandLine(&ts) == "cGFzcw==\r\n");
+    assert(auth3.isComplete());
+    assert(auth3.needsResponse());
+    r.clear();
+    r.parseLine("250 OK");
+    assert(auth3.processResponse(r, &ts) == true);
+    assert(!auth3.needsResponse());
+    assert(!smtp.lastErrorCode);
+    assert(ts == ts2);
+
+    //
+    // MAIL FROM:
+    //
+
+    smtp.clear();
+    MailFromCommand mail(&smtp, "joe at user.org");
+    // flags
+    assert(!mail.closeConnectionOnError());
+    assert(!mail.mustBeLastInPipeline());
+    assert(!mail.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!mail.isComplete());
+    assert(!mail.doNotExecute(0));
+    assert(!mail.needsResponse());
+
+    // dynamics: success, no size, no 8bit
+    ts.clear();
+    ts2 = ts;
+    assert(mail.nextCommandLine(&ts) == "MAIL FROM:<joe at user.org>\r\n");
+    assert(ts2 == ts);
+    assert(mail.isComplete());
+    assert(mail.needsResponse());
+    r.clear();
+    r.parseLine("250 Ok");
+    assert(mail.processResponse(r, &ts) == true);
+    assert(!mail.needsResponse());
+    assert(ts == ts2);
+    assert(smtp.lastErrorCode == 0);
+
+    // dynamics: success, size, 8bit, but no SIZE, 8BITMIME caps
+    smtp.clear();
+    MailFromCommand mail2(&smtp, "joe at user.org", true, 500);
+    ts.clear();
+    ts2 = ts;
+    assert(mail2.nextCommandLine(&ts) == "MAIL FROM:<joe at user.org>\r\n");
+    assert(ts == ts2);
+
+    // dynamics: success, size, 8bit, SIZE, 8BITMIME caps
+    smtp.clear();
+    MailFromCommand mail3(&smtp, "joe at user.org", true, 500);
+    ts.clear();
+    ts2 = ts;
+    smtp.caps << QStringLiteral("SIZE") << QStringLiteral("8BITMIME");
+    assert(mail3.nextCommandLine(&ts) == "MAIL FROM:<joe at user.org> BODY=8BITMIME SIZE=500\r\n");
+    assert(ts == ts2);
+
+    // dynamics: failure
+    smtp.clear();
+    MailFromCommand mail4(&smtp, "joe at user.org");
+    ts.clear();
+    mail4.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("503 Bad sequence of commands");
+    assert(mail4.processResponse(r, &ts) == false);
+    assert(mail4.isComplete());
+    assert(!mail4.needsResponse());
+    assert(ts.failed());
+    assert(!ts.failedFatally());
+    assert(smtp.lastErrorCode == 0);
+
+    //
+    // RCPT TO:
+    //
+
+    smtp.clear();
+    RcptToCommand rcpt(&smtp, "joe at user.org");
+    // flags
+    assert(!rcpt.closeConnectionOnError());
+    assert(!rcpt.mustBeLastInPipeline());
+    assert(!rcpt.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!rcpt.isComplete());
+    assert(!rcpt.doNotExecute(0));
+    assert(!rcpt.needsResponse());
+
+    // dynamics: success
+    ts.clear();
+    ts2 = ts;
+    assert(rcpt.nextCommandLine(&ts) == "RCPT TO:<joe at user.org>\r\n");
+    assert(ts == ts2);
+    assert(rcpt.isComplete());
+    assert(rcpt.needsResponse());
+    r.clear();
+    r.parseLine("250 Ok");
+    assert(rcpt.processResponse(r, &ts) == true);
+    assert(!rcpt.needsResponse());
+    assert(ts.atLeastOneRecipientWasAccepted());
+    assert(!ts.haveRejectedRecipients());
+    assert(!ts.failed());
+    assert(!ts.failedFatally());
+    assert(smtp.lastErrorCode == 0);
+
+    // dynamics: failure
+    smtp.clear();
+    RcptToCommand rcpt2(&smtp, "joe at user.org");
+    ts.clear();
+    rcpt2.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("530 5.7.1 Relaying not allowed!");
+    assert(rcpt2.processResponse(r, &ts) == false);
+    assert(rcpt2.isComplete());
+    assert(!rcpt2.needsResponse());
+    assert(!ts.atLeastOneRecipientWasAccepted());
+    assert(ts.haveRejectedRecipients());
+    assert(ts.rejectedRecipients().count() == 1);
+    assert(ts.rejectedRecipients().front().recipient == QLatin1String("joe at user.org"));
+    assert(ts.failed());
+    assert(!ts.failedFatally());
+    assert(smtp.lastErrorCode == 0);
+
+    // dynamics: success and failure combined
+    smtp.clear();
+    RcptToCommand rcpt3(&smtp, "info at example.com");
+    RcptToCommand rcpt4(&smtp, "halloween at microsoft.com");
+    RcptToCommand rcpt5(&smtp, "joe at user.org");
+    ts.clear();
+    rcpt3.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("530 5.7.1 Relaying not allowed!");
+    rcpt3.processResponse(r, &ts);
+
+    rcpt4.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("250 Ok");
+    rcpt4.processResponse(r, &ts);
+
+    rcpt5.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("250 Ok");
+    assert(ts.failed());
+    assert(!ts.failedFatally());
+    assert(ts.haveRejectedRecipients());
+    assert(ts.atLeastOneRecipientWasAccepted());
+    assert(smtp.lastErrorCode == 0);
+
+    //
+    // DATA (init)
+    //
+
+    smtp.clear();
+    DataCommand data(&smtp);
+    // flags
+    assert(!data.closeConnectionOnError());
+    assert(data.mustBeLastInPipeline());
+    assert(!data.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!data.isComplete());
+    assert(!data.doNotExecute(0));
+    assert(!data.needsResponse());
+
+    // dynamics: success
+    ts.clear();
+    assert(data.nextCommandLine(&ts) == "DATA\r\n");
+    assert(data.isComplete());
+    assert(data.needsResponse());
+    assert(ts.dataCommandIssued());
+    assert(!ts.dataCommandSucceeded());
+    r.clear();
+    r.parseLine("354 Send data, end in <CR><LF>.<CR><LF>");
+    assert(data.processResponse(r, &ts) == true);
+    assert(!data.needsResponse());
+    assert(ts.dataCommandSucceeded());
+    assert(ts.dataResponse() == r);
+    assert(smtp.lastErrorCode == 0);
+
+    // dynamics: failure
+    smtp.clear();
+    DataCommand data2(&smtp);
+    ts.clear();
+    data2.nextCommandLine(&ts);
+    r.clear();
+    r.parseLine("551 No valid recipients");
+    assert(data2.processResponse(r, &ts) == false);
+    assert(!data2.needsResponse());
+    assert(!ts.dataCommandSucceeded());
+    assert(ts.dataResponse() == r);
+    assert(smtp.lastErrorCode == 0);
+
+    //
+    // DATA (transfer)
+    //
+
+    TransferCommand xfer(&smtp, 0);
+    // flags
+    assert(!xfer.closeConnectionOnError());
+    assert(!xfer.mustBeLastInPipeline());
+    assert(xfer.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!xfer.isComplete());
+    assert(!xfer.needsResponse());
+
+    // dynamics 1: DATA command failed
+    ts.clear();
+    r.clear();
+    r.parseLine("551 no valid recipients");
+    ts.setDataCommandIssued(true);
+    ts.setDataCommandSucceeded(false, r);
+    assert(xfer.doNotExecute(&ts));
+
+    // dynamics 2: some recipients rejected, but not all
+    smtp.clear();
+    TransferCommand xfer2(&smtp, 0);
+    ts.clear();
+    ts.setRecipientAccepted();
+    ts.addRejectedRecipient(QStringLiteral("joe at user.org"), QStringLiteral("No relaying allowed"));
+    ts.setDataCommandIssued(true);
+    r.clear();
+    r.parseLine("354 go on");
+    ts.setDataCommandSucceeded(true, r);
+    // ### will change with allow-partial-delivery option:
+    assert(xfer.doNotExecute(&ts));
+
+    // successful dynamics with all combinations of:
+    enum {
+        EndInLF = 1,
+        PerformDotStuff = 2,
+        UngetLast = 4,
+        Preloading = 8,
+        Error = 16,
+        EndOfOptions = 32
+    };
+    for (unsigned int i = 0; i < EndOfOptions; ++i)
+        checkSuccessfulTransferCommand(i & Error, i & Preloading, i & UngetLast,
+                                       i & PerformDotStuff, i & EndInLF);
+
+    //
+    // NOOP
+    //
+
+    smtp.clear();
+    NoopCommand noop(&smtp);
+    // flags
+    assert(!noop.closeConnectionOnError());
+    assert(noop.mustBeLastInPipeline());
+    assert(!noop.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!noop.isComplete());
+    assert(!noop.doNotExecute(&ts));
+    assert(!noop.needsResponse());
+
+    // dynamics: success (failure is tested with RSET)
+    assert(noop.nextCommandLine(0) == "NOOP\r\n");
+    assert(noop.isComplete());
+    assert(noop.needsResponse());
+    r.clear();
+    r.parseLine("250 Ok");
+    assert(noop.processResponse(r, 0) == true);
+    assert(noop.isComplete());
+    assert(!noop.needsResponse());
+    assert(smtp.lastErrorCode == 0);
+    assert(smtp.lastErrorMessage.isNull());
+
+    //
+    // RSET
+    //
+
+    smtp.clear();
+    RsetCommand rset(&smtp);
+    // flags
+    assert(rset.closeConnectionOnError());
+    assert(!rset.mustBeLastInPipeline());
+    assert(!rset.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!rset.isComplete());
+    assert(!rset.doNotExecute(&ts));
+    assert(!rset.needsResponse());
+
+    // dynamics: failure (success is tested with NOOP/QUIT)
+    assert(rset.nextCommandLine(0) == "RSET\r\n");
+    assert(rset.isComplete());
+    assert(rset.needsResponse());
+    r.clear();
+    r.parseLine("502 command not implemented");
+    assert(rset.processResponse(r, 0) == false);
+    assert(rset.isComplete());
+    assert(!rset.needsResponse());
+    assert(smtp.lastErrorCode == 0);   // an RSET failure isn't worth it, is it?
+    assert(smtp.lastErrorMessage.isNull());
+
+    //
+    // QUIT
+    //
+
+    smtp.clear();
+    QuitCommand quit(&smtp);
+    // flags
+    assert(quit.closeConnectionOnError());
+    assert(quit.mustBeLastInPipeline());
+    assert(!quit.mustBeFirstInPipeline());
+
+    // initial state
+    assert(!quit.isComplete());
+    assert(!quit.doNotExecute(0));
+    assert(!quit.needsResponse());
+
+    // dynamics 1: success
+    assert(quit.nextCommandLine(0) == "QUIT\r\n");
+    assert(quit.isComplete());
+    assert(quit.needsResponse());
+    r.clear();
+    r.parseLine("221 Goodbye");
+    assert(quit.processResponse(r, 0) == true);
+    assert(quit.isComplete());
+    assert(!quit.needsResponse());
+    assert(smtp.lastErrorCode == 0);
+    assert(smtp.lastErrorMessage.isNull());
+
+    // dynamics 2: success
+    smtp.clear();
+    QuitCommand quit2(&smtp);
+    quit2.nextCommandLine(0);
+    r.clear();
+    r.parseLine("500 unknown command");
+    assert(quit2.processResponse(r, 0) == false);
+    assert(quit2.isComplete());
+    assert(!quit2.needsResponse());
+    assert(smtp.lastErrorCode == 0);   // an QUIT failure isn't worth it, is it?
+    assert(smtp.lastErrorMessage.isNull());
+
+    return 0;
+}
+
+void checkSuccessfulTransferCommand(bool error, bool preload, bool ungetLast,
+                                    bool slaveDotStuff, bool mailEndsInNewline)
+{
+    qDebug() << "   ===== checkTransferCommand( "
+             << error << ", "
+             << preload << ", "
+             << ungetLast << ", "
+             << slaveDotStuff << ", "
+             << mailEndsInNewline << " ) =====" << endl;
+
+    FakeSession smtp;
+    if (slaveDotStuff) {
+        smtp.lf2crlfAndDotStuff = true;
+    }
+
+    Response r;
+
+    const char *s_pre = slaveDotStuff ?
+                        mailEndsInNewline ? foobarbaz_lf : foobarbaz
+                        :
+                        mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed;
+    const unsigned int s_pre_len = qstrlen(s_pre);
+
+    const char *s_post = mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed;
+    //const unsigned int s_post_len = qstrlen( s_post );
+
+    TransferCommand xfer(&smtp, preload ? s_post : 0);
+
+    TransactionState ts;
+    ts.setRecipientAccepted();
+    ts.setDataCommandIssued(true);
+    r.clear();
+    r.parseLine("354 ok");
+    ts.setDataCommandSucceeded(true, r);
+    assert(!xfer.doNotExecute(&ts));
+    if (preload) {
+        assert(xfer.nextCommandLine(&ts) == s_post);
+        assert(!xfer.isComplete());
+        assert(!xfer.needsResponse());
+        assert(!ts.failed());
+        assert(smtp.lastErrorCode == 0);
+    }
+    smtp.nextData = QByteArray(s_pre, s_pre_len);
+    smtp.nextDataReturnCode = s_pre_len;
+    assert(xfer.nextCommandLine(&ts) == s_post);
+    assert(!xfer.isComplete());
+    assert(!xfer.needsResponse());
+    assert(!ts.failed());
+    assert(smtp.lastErrorCode == 0);
+    smtp.nextData.resize(0);
+    smtp.nextDataReturnCode = 0;
+    if (ungetLast) {
+        xfer.ungetCommandLine(xfer.nextCommandLine(&ts), &ts);
+        assert(!xfer.isComplete());
+        assert(!xfer.needsResponse());
+        assert(!ts.complete());
+        smtp.nextDataReturnCode = -1; // double read -> error
+    }
+    if (mailEndsInNewline) {
+        assert(xfer.nextCommandLine(&ts) == ".\r\n");
+    } else {
+        assert(xfer.nextCommandLine(&ts) == "\r\n.\r\n");
+    }
+    assert(xfer.isComplete());
+    assert(xfer.needsResponse());
+    assert(!ts.complete());
+    assert(!ts.failed());
+    assert(smtp.lastErrorCode == 0);
+    r.clear();
+    if (error) {
+        r.parseLine("552 Exceeded storage allocation");
+        assert(xfer.processResponse(r, &ts) == false);
+        assert(!xfer.needsResponse());
+        assert(ts.complete());
+        assert(ts.failed());
+        assert(smtp.lastErrorCode == KIO::ERR_DISK_FULL);
+    } else {
+        r.parseLine("250 Message accepted");
+        assert(xfer.processResponse(r, &ts) == true);
+        assert(!xfer.needsResponse());
+        assert(ts.complete());
+        assert(!ts.failed());
+        assert(smtp.lastErrorCode == 0);
+    }
+}
+
+#ifndef NDEBUG
+# define NDEBUG
+#endif
+
+#include "command.cpp"
+#include "response.cpp"
+#include "transactionstate.cpp"
diff --git a/kioslave/src/smtp/tests/test_headergeneration.cpp b/kioslave/src/smtp/tests/test_headergeneration.cpp
new file mode 100644
index 0000000..9a55b8c
--- /dev/null
+++ b/kioslave/src/smtp/tests/test_headergeneration.cpp
@@ -0,0 +1,90 @@
+#include "../request.h"
+
+#include <iostream>
+
+//using std::cout;
+//using std::endl;
+
+int main(int, char **)
+{
+    static QByteArray expected =
+        "From: mutz at kde.org\r\n"
+        "Subject: missing subject\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n"
+        "From: Marc Mutz <mutz at kde.org>\r\n"
+        "Subject: missing subject\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n"
+        "From: \"Mutz, Marc\" <mutz at kde.org>\r\n"
+        "Subject: missing subject\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n"
+        "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= <mutz at kde.org>\r\n"
+        "Subject: missing subject\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n"
+        "From: mutz at kde.org\r\n"
+        "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n"
+        "From: Marc Mutz <mutz at kde.org>\r\n"
+        "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n"
+        "From: \"Mutz, Marc\" <mutz at kde.org>\r\n"
+        "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n"
+        "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= <mutz at kde.org>\r\n"
+        "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n"
+        "To: joe at user.org,\r\n"
+        "\tvalentine at 14th.february.org\r\n"
+        "Cc: boss at example.com\r\n"
+        "\n";
+
+    KioSMTP::Request request;
+    QByteArray result;
+
+    request.setEmitHeaders(true);
+    request.setFromAddress(QStringLiteral("mutz at kde.org"));
+    request.addTo(QStringLiteral("joe at user.org"));
+    request.addTo(QStringLiteral("valentine at 14th.february.org"));
+    request.addCc(QStringLiteral("boss at example.com"));
+
+    result += request.headerFields() + '\n';
+    result += request.headerFields(QStringLiteral("Marc Mutz")) + '\n';
+    result += request.headerFields(QStringLiteral("Mutz, Marc")) + '\n';
+    result += request.headerFields(QString::fromUtf8("Marc Mötz")) + '\n';
+
+    request.setSubject(QString::fromUtf8("Blödes Subject"));
+
+    result += request.headerFields() + '\n';
+    result += request.headerFields(QStringLiteral("Marc Mutz")) + '\n';
+    result += request.headerFields(QStringLiteral("Mutz, Marc")) + '\n';
+    result += request.headerFields(QString::fromUtf8("Marc Mötz")) + '\n';
+
+    if (result != expected) {
+        std::cout << "Result:\n" << result.data() << std::endl;
+        std::cout << "Expected:\n" << expected.data() << std::endl;
+    }
+
+    return result == expected ? 0 : 1;
+}
+
+#include "../request.cpp"
+
diff --git a/kioslave/src/smtp/tests/test_responseparser.cpp b/kioslave/src/smtp/tests/test_responseparser.cpp
new file mode 100644
index 0000000..3d449e5
--- /dev/null
+++ b/kioslave/src/smtp/tests/test_responseparser.cpp
@@ -0,0 +1,106 @@
+#include "test_responseparser.h"
+#include "../response.h"
+
+#include <qtest.h>
+#include <assert.h>
+
+QTEST_GUILESS_MAIN(ResponseParserTest)
+
+static const QByteArray singleLineResponseCRLF = "250 OK\r\n";
+static const QByteArray singleLineResponse     = "250 OK";
+
+static const QByteArray multiLineResponse[] = {
+    "250-ktown.kde.org\r\n",
+    "250-STARTTLS\r\n",
+    "250-AUTH PLAIN DIGEST-MD5\r\n",
+    "250 PIPELINING\r\n"
+};
+static const unsigned int numMultiLineLines = sizeof multiLineResponse / sizeof * multiLineResponse;
+
+void ResponseParserTest::testResponseParser()
+{
+    KioSMTP::Response r;
+    QVERIFY(r.isValid());
+    QVERIFY(r.lines().empty());
+    QVERIFY(r.isWellFormed());
+    QCOMPARE(r.code(), 0u);
+    QVERIFY(r.isUnknown());
+    QVERIFY(!r.isComplete());
+    QVERIFY(!r.isOk());
+    r.parseLine(singleLineResponseCRLF.data(), singleLineResponseCRLF.length());
+    QVERIFY(r.isWellFormed());
+    QVERIFY(r.isComplete());
+    QVERIFY(r.isValid());
+    QVERIFY(r.isPositive());
+    QVERIFY(r.isOk());
+    QCOMPARE(r.code(), 250u);
+    QCOMPARE(r.errorCode(), 0);
+    QCOMPARE(r.first(), 2u);
+    QCOMPARE(r.second(), 5u);
+    QCOMPARE(r.third(), 0u);
+    QCOMPARE(r.lines().count(), 1);
+    QCOMPARE(r.lines().front(), QByteArray("OK"));
+    r.parseLine(singleLineResponse.data(), singleLineResponse.length());
+    QVERIFY(!r.isValid());
+    r.clear();
+    QVERIFY(r.isValid());
+    QVERIFY(r.lines().empty());
+
+    r.parseLine(singleLineResponse.data(), singleLineResponse.length());
+    QVERIFY(r.isWellFormed());
+    QVERIFY(r.isComplete());
+    QVERIFY(r.isValid());
+    QVERIFY(r.isPositive());
+    QVERIFY(r.isOk());
+    QCOMPARE(r.code(), 250u);
+    QCOMPARE(r.first(), 2u);
+    QCOMPARE(r.second(), 5u);
+    QCOMPARE(r.third(), 0u);
+    QCOMPARE(r.lines().count(), 1);
+    QCOMPARE(r.lines().front(), QByteArray("OK"));
+    r.parseLine(singleLineResponse.data(), singleLineResponse.length());
+    QVERIFY(!r.isValid());
+    r.clear();
+    QVERIFY(r.isValid());
+
+    for (unsigned int i = 0; i < numMultiLineLines; ++i) {
+        r.parseLine(multiLineResponse[i].data(), multiLineResponse[i].length());
+        QVERIFY(r.isWellFormed());
+        if (i < numMultiLineLines - 1) {
+            QVERIFY(!r.isComplete());
+        } else {
+            QVERIFY(r.isComplete());
+        }
+        QVERIFY(r.isValid());
+        QVERIFY(r.isPositive());
+        QCOMPARE(r.code(), 250u);
+        QCOMPARE(r.first(), 2u);
+        QCOMPARE(r.second(), 5u);
+        QCOMPARE(r.third(), 0u);
+        QCOMPARE(r.lines().count(), (int)i + 1);
+    }
+    QCOMPARE(r.lines().back(), QByteArray("PIPELINING"));
+
+    r.clear();
+    r.parseLine("230", 3);
+    QVERIFY(r.isValid());
+    QVERIFY(r.isWellFormed());   // even though it isn't ;-)
+    QCOMPARE(r.code(), 230u);
+    QCOMPARE(r.lines().count(), 1);
+    QVERIFY(r.lines().front().isNull());
+
+    r.clear();
+    r.parseLine("230\r\n", 5);
+    QVERIFY(r.isValid());
+    QVERIFY(r.isWellFormed());   // even though it isn't ;-)
+    QCOMPARE(r.code(), 230u);
+    QCOMPARE(r.lines().count(), 1);
+    QVERIFY(r.lines().front().isNull());
+
+    r.clear();
+    r.parseLine(" 23 ok", 6);
+    QVERIFY(!r.isValid());
+    QVERIFY(!r.isWellFormed());
+}
+
+#include "../response.cpp"
diff --git a/kioslave/src/smtp/tests/test_responseparser.h b/kioslave/src/smtp/tests/test_responseparser.h
new file mode 100644
index 0000000..789a45f
--- /dev/null
+++ b/kioslave/src/smtp/tests/test_responseparser.h
@@ -0,0 +1,32 @@
+/*
+    Copyright (c) 2006 Volker Krause <vkrause at 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 RESPONSEPARSER_TEST_H
+#define RESPONSEPARSER_TEST_H
+
+#include <QtCore/QObject>
+
+class ResponseParserTest : public QObject
+{
+    Q_OBJECT
+private Q_SLOTS:
+    void testResponseParser();
+};
+
+#endif
diff --git a/kioslave/src/smtp/transactionstate.cpp b/kioslave/src/smtp/transactionstate.cpp
new file mode 100644
index 0000000..58910df
--- /dev/null
+++ b/kioslave/src/smtp/transactionstate.cpp
@@ -0,0 +1,127 @@
+/*  -*- c++ -*-
+    transactionstate.cc
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#include "transactionstate.h"
+
+#include <kio/global.h>
+#include <KLocalizedString>
+
+namespace KioSMTP
+{
+
+void TransactionState::setFailedFatally(int code, const QString &msg)
+{
+    mFailed = mFailedFatally = true;
+    mErrorCode = code;
+    mErrorMessage = msg;
+}
+
+void TransactionState::setMailFromFailed(const QString &addr, const Response &r)
+{
+    setFailed();
+    mErrorCode = KIO::ERR_NO_CONTENT;
+    if (addr.isEmpty()) {
+        mErrorMessage = i18n("The server did not accept a blank sender address.\n"
+                             "%1",  r.errorMessage());
+    } else {
+        mErrorMessage = i18n("The server did not accept the sender address \"%1\".\n"
+                             "%2",  addr,  r.errorMessage());
+    }
+}
+
+void TransactionState::addRejectedRecipient(const RecipientRejection &r)
+{
+    mRejectedRecipients.push_back(r);
+    if (mRcptToDenyIsFailure) {
+        setFailed();
+    }
+}
+
+void TransactionState::setDataCommandSucceeded(bool succeeded, const Response &r)
+{
+    mDataCommandSucceeded = succeeded;
+    mDataResponse = r;
+    if (!succeeded) {
+        setFailed();
+    } else if (failed()) {
+        // can happen with pipelining: the server accepts the DATA, but
+        // we don't want to send the data, so force a connection
+        // shutdown:
+        setFailedFatally();
+    }
+}
+
+int TransactionState::errorCode() const
+{
+    if (!failed()) {
+        return 0;
+    }
+    if (mErrorCode) {
+        return mErrorCode;
+    }
+    if (haveRejectedRecipients() || !dataCommandSucceeded()) {
+        return KIO::ERR_NO_CONTENT;
+    }
+    // ### what else?
+    return KIO::ERR_INTERNAL;
+}
+
+QString TransactionState::errorMessage() const
+{
+    if (!failed()) {
+        return QString();
+    }
+
+    if (!mErrorMessage.isEmpty()) {
+        return mErrorMessage;
+    }
+
+    if (haveRejectedRecipients()) {
+        QStringList recip;
+        recip.reserve(mRejectedRecipients.count());
+        for (RejectedRecipientList::const_iterator it = mRejectedRecipients.begin();
+                it != mRejectedRecipients.end(); ++it) {
+            recip.push_back((*it).recipient + QLatin1String(" (") + (*it).reason + QLatin1Char(')'));
+        }
+        return i18n("Message sending failed since the following recipients were rejected by the server:\n"
+                    "%1", recip.join(QStringLiteral("\n")));
+    }
+
+    if (!dataCommandSucceeded()) {
+        return i18n("The attempt to start sending the message content failed.\n"
+                    "%1", mDataResponse.errorMessage());
+    }
+
+    // ### what else?
+    return i18n("Unhandled error condition. Please send a bug report.");
+}
+
+}
diff --git a/kioslave/src/smtp/transactionstate.h b/kioslave/src/smtp/transactionstate.h
new file mode 100644
index 0000000..ffad510
--- /dev/null
+++ b/kioslave/src/smtp/transactionstate.h
@@ -0,0 +1,230 @@
+/*  -*- c++ -*-
+    transactionstate.h
+
+    This file is part of kio_smtp, the KDE SMTP kioslave.
+    Copyright (c) 2003 Marc Mutz <mutz at kde.org>
+
+    This program is free software; you can redistribute it and/or modify it
+    under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    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
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of this program with any edition of
+    the Qt library by Trolltech AS, Norway (or with modified versions
+    of Qt that use the same license as Qt), and distribute linked
+    combinations including the two.  You must obey the GNU General
+    Public License in all respects for all of the code used other than
+    Qt.  If you modify this file, you may extend this exception to
+    your version of the file, but you are not obligated to do so.  If
+    you do not wish to do so, delete this exception statement from
+    your version.
+*/
+
+#ifndef __KIOSMTP_TRANSACTIONSTATE_H__
+#define __KIOSMTP_TRANSACTIONSTATE_H__
+
+#include "response.h"
+
+#include <QString>
+
+namespace KioSMTP
+{
+
+/**
+   @short A class modelling an SMTP transaction's state
+
+   This class models SMTP transaction state, ie. the collective
+   result of the MAIL FROM:, RCPT TO: and DATA commands. This is
+   needed since e.g. a single failed RCPT TO: command does not
+   necessarily fail the whole transaction (servers are free to
+   accept delivery for some recipients, but not for others).
+
+   The class can operate in two modes, which differ in the way
+   failed recipients are handled. If @p rcptToDenyIsFailure is true
+   (the default), then any failing RCPT TO: will cause the
+   transaction to fail. Since at the point of RCPT TO: failure
+   detection, the DATA command may have already been sent
+   (pipelining), the only way to cancel the transaction is to take
+   down the connection hard (ie. without proper quit).
+
+   Since that is not very nice behaviour, a second mode that is more
+   to the spirit of SMTP is provided that can cope with partially
+   failed RCPT TO: commands.
+*/
+class TransactionState
+{
+public:
+    struct RecipientRejection {
+        RecipientRejection(const QString &who = QString(),
+                           const QString &why = QString())
+            : recipient(who)
+            , reason(why)
+        {
+        }
+        QString recipient;
+        QString reason;
+#ifdef KIOSMTP_COMPARATORS
+        bool operator==(const RecipientRejection &other) const
+        {
+            return recipient == other.recipient && reason == other.reason;
+        }
+#endif
+    };
+    typedef QList<RecipientRejection> RejectedRecipientList;
+
+    TransactionState(bool rcptToDenyIsFailure = true)
+        : mErrorCode(0)
+        , mRcptToDenyIsFailure(rcptToDenyIsFailure)
+        , mAtLeastOneRecipientWasAccepted(false)
+        , mDataCommandIssued(false)
+        , mDataCommandSucceeded(false)
+        , mFailed(false)
+        , mFailedFatally(false)
+        , mComplete(false)
+    {
+    }
+
+    /**
+     * @return whether the transaction failed (e.g. the server
+     * rejected all recipients. Graceful failure is handled after
+     * transaction ends.
+     */
+    bool failed() const
+    {
+        return mFailed || mFailedFatally;
+    }
+    void setFailed()
+    {
+        mFailed = true;
+    }
+
+    /**
+     * @return whether the failure was so grave that an immediate
+     * untidy connection shutdown is in order (ie. @ref
+     * smtp_close(false)). Fatal failure is handled immediately
+     */
+    bool failedFatally() const
+    {
+        return mFailedFatally;
+    }
+    void setFailedFatally(int code = 0, const QString &msg = QString());
+
+    /** @return whether the transaction was completed successfully */
+    bool complete() const
+    {
+        return mComplete;
+    }
+    void setComplete()
+    {
+        mComplete = true;
+    }
+
+    /**
+     * @return an appropriate KIO error code in case the transaction
+     * failed, or 0 otherwise
+     */
+    int errorCode() const;
+
+    /**
+     * @return an appropriate error message in case the transaction
+     * failed or QString() otherwise
+     */
+    QString errorMessage() const;
+
+    void setMailFromFailed(const QString &addr, const Response &r);
+
+    bool dataCommandIssued() const
+    {
+        return mDataCommandIssued;
+    }
+    void setDataCommandIssued(bool issued)
+    {
+        mDataCommandIssued = issued;
+    }
+
+    bool dataCommandSucceeded() const
+    {
+        return mDataCommandIssued && mDataCommandSucceeded;
+    }
+    void setDataCommandSucceeded(bool succeeded, const Response &r);
+
+    Response dataResponse() const
+    {
+        return mDataResponse;
+    }
+
+    bool atLeastOneRecipientWasAccepted() const
+    {
+        return mAtLeastOneRecipientWasAccepted;
+    }
+    void setRecipientAccepted()
+    {
+        mAtLeastOneRecipientWasAccepted = true;
+    }
+
+    bool haveRejectedRecipients() const
+    {
+        return !mRejectedRecipients.empty();
+    }
+    RejectedRecipientList rejectedRecipients() const
+    {
+        return mRejectedRecipients;
+    }
+    void addRejectedRecipient(const RecipientRejection &r);
+    void addRejectedRecipient(const QString &who, const QString &why)
+    {
+        addRejectedRecipient(RecipientRejection(who, why));
+    }
+
+    void clear()
+    {
+        mRejectedRecipients.clear();
+        mDataResponse.clear();
+        mAtLeastOneRecipientWasAccepted
+            = mDataCommandIssued
+              = mDataCommandSucceeded
+                = mFailed = mFailedFatally
+                            = mComplete = false;
+    }
+
+#ifdef KIOSMTP_COMPARATORS
+    bool operator==(const TransactionState &other) const
+    {
+        return
+            mAtLeastOneRecipientWasAccepted == other.mAtLeastOneRecipientWasAccepted &&
+            mDataCommandIssued == other.mDataCommandIssued &&
+            mDataCommandSucceeded == other.mDataCommandSucceeded &&
+            mFailed == other.mFailed &&
+            mFailedFatally == other.mFailedFatally &&
+            mComplete == other.mComplete &&
+            mDataResponse.code() == other.mDataResponse.code() &&
+            mRejectedRecipients == other.mRejectedRecipients;
+    }
+#endif
+
+private:
+    RejectedRecipientList mRejectedRecipients;
+    Response mDataResponse;
+    QString mErrorMessage;
+    int mErrorCode;
+    bool mRcptToDenyIsFailure;
+    bool mAtLeastOneRecipientWasAccepted;
+    bool mDataCommandIssued;
+    bool mDataCommandSucceeded;
+    bool mFailed;
+    bool mFailedFatally;
+    bool mComplete;
+};
+
+} // namespace KioSMTP
+
+#endif // __KIOSMTP_TRANSACTIONSTATE_H__
diff --git a/kmailtransport.categories b/kmailtransport.categories
new file mode 100644
index 0000000..029e708
--- /dev/null
+++ b/kmailtransport.categories
@@ -0,0 +1,3 @@
+log_smtp kioslave (smtp)
+log_mailtransport kmailtransport (kmailtransport)
+


More information about the kde-doc-english mailing list