[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