diff --git a/CMakeLists.txt b/CMakeLists.txt index aedcc6e80..cad0d9e75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,181 +1,185 @@ project(kdepimlibs) # where to look first for cmake modules. This line must be the first one or cmake will use the system's FindFoo.cmake set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") ############### Build Options ############### option(KDEPIM_ONLY_KLEO "Only build the libraries needed by Kleopatra." FALSE) ############### The kdepimlibs version (used e.g. in KdepimLibsConfig.cmake) ############### set(KDEPIMLIBS_VERSION_MAJOR 4) set(KDEPIMLIBS_VERSION_MINOR 3) set(KDEPIMLIBS_VERSION_PATCH 62) set(KDEPIMLIBS_VERSION ${KDEPIMLIBS_VERSION_MAJOR}.${KDEPIMLIBS_VERSION_MINOR}.${KDEPIMLIBS_VERSION_PATCH}) ############### search packages used by KDE ############### find_package(KDE4 4.3.62 REQUIRED) include(KDE4Defaults) include(MacroLibrary) ############### Needed commands before building anything ############### add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}) ############### Find the stuff we need ############### set(Boost_MINIMUM_VERSION "1.33.1") -find_package(Boost ${Boost_MINIMUM_VERSION} COMPONENTS graph) +if (WIN32) + find_package(Boost ${Boost_MINIMUM_VERSION}) # (jstaniek) temp. fix unless we have graph library, not just headers +else (WIN32) + find_package(Boost ${Boost_MINIMUM_VERSION} COMPONENTS graph) +endif (WIN32) macro_log_feature(Boost_FOUND "Boost" "Boost C++ Libraries" "http://www.boost.org" TRUE ${Boost_MINIMUM_VERSION} "The Boost libraries boost and boost-graph are required by several critical KDEPIM apps.") #FindGpgme.cmake already handles the log message but we must ensure it is required. find_package(Gpgme REQUIRED) # configure macros if (GPGME_FOUND) include (gpgme++/ConfigureChecks.cmake) endif (GPGME_FOUND) if (NOT KDEPIM_ONLY_KLEO) #FindAkonadi.cmake is only there for compatibility reasons, but we don't want to use that. find_package(Akonadi 1.2.60 QUIET NO_MODULE) macro_log_feature(Akonadi_FOUND "Akonadi" "Akonadi server libraries (from kdesupport)" "http://pim.kde.org/akonadi" TRUE "1.2.60" "Akonadi is required to build KdepimLibs.") find_package(Sasl2) macro_log_feature(SASL2_FOUND "cyrus-sasl" "Cyrus SASL API" "http://asg.web.cmu.edu/sasl/sasl-library.html" TRUE "" "Required to support authentication of logins in the IMAP and Sieve kioslaves.") include (ConfigureChecks.cmake) set(SHARED_MIME_INFO_MINIMUM_VERSION "0.30") find_package(SharedMimeInfo) macro_log_feature(SHARED_MIME_INFO_FOUND "SMI" "SharedMimeInfo" "http://freedesktop.org/wiki/Software/shared-mime-info" TRUE "0.30" "SharedMimeInfo is required.") endif (NOT KDEPIM_ONLY_KLEO) ############### Now, we add the KDEPIMLibs components ############### # These targets will always be built add_subdirectory(cmake) add_subdirectory(gpgme++) add_subdirectory(qgpgme) if (NOT KDEPIM_ONLY_KLEO) add_subdirectory(akonadi) add_subdirectory(kabc) add_subdirectory(kblog) add_subdirectory(kcal) add_subdirectory(kholidays) add_subdirectory(kimap) add_subdirectory(kioslave) add_subdirectory(kldap) add_subdirectory(kmime) add_subdirectory(kpimidentities) add_subdirectory(kpimutils) add_subdirectory(kpimtextedit) add_subdirectory(kresources) add_subdirectory(ktnef) add_subdirectory(kxmlrpcclient) add_subdirectory(mailtransport) add_subdirectory(microblog) add_subdirectory(syndication) # Build the CamelCase headers add_subdirectory(includes) endif (NOT KDEPIM_ONLY_KLEO) # doc must be a subdir of kdepimlibs macro_optional_add_subdirectory(doc) # All done, let's display what we found... macro_display_feature_log() ############### Here we install some extra stuff ############### if (NOT KDEPIM_ONLY_KLEO) install(FILES kdepimlibs-mime.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) endif (NOT KDEPIM_ONLY_KLEO) # now create the KdepimLibsConfig.cmake file, which will be loaded by # kdelibs/cmake/modules/FindKdepimLibs.cmake and which has to contain all information # about the installed kdepimlibs anybody would like to have. Alex # we need the absolute directories where stuff will be installed too # but since the variables which contain the destinations can be relative # or absolute paths, we need this macro to make them all absoulte, Alex macro(MAKE_INSTALL_PATH_ABSOLUTE out in) if (IS_ABSOLUTE "${in}") # IS_ABSOLUTE is new since cmake 2.4.8 set(${out} "${in}") else (IS_ABSOLUTE "${in}") set(${out} "\${KDEPIMLIBS_INSTALL_DIR}/${in}") endif (IS_ABSOLUTE "${in}") endmacro(MAKE_INSTALL_PATH_ABSOLUTE out in) # all the following variables are put into KdepimLibsConfig.cmake, so # they are usable by projects using kdepimlibs. Alex make_install_path_absolute(KDEPIMLIBS_DATA_DIR ${DATA_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_DBUS_INTERFACES_DIR ${DBUS_INTERFACES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_DBUS_SERVICES_DIR ${DBUS_SERVICES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_INCLUDE_DIR ${INCLUDE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LIB_DIR ${LIB_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_BIN_DIR ${BIN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LIBEXEC_DIR ${LIBEXEC_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SBIN_DIR ${SBIN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_HTML_DIR ${HTML_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_CONFIG_DIR ${CONFIG_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_ICON_DIR ${ICON_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_KCFG_DIR ${KCFG_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_LOCALE_DIR ${LOCALE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_MIME_DIR ${MIME_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SOUND_DIR ${SOUND_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_TEMPLATES_DIR ${TEMPLATES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_WALLPAPER_DIR ${WALLPAPER_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_KCONF_UPDATE_DIR ${KCONF_UPDATE_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_AUTOSTART_DIR ${AUTOSTART_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_XDG_APPS_DIR ${XDG_APPS_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_XDG_DIRECTORY_DIR ${XDG_DIRECTORY_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SYSCONF_DIR ${SYSCONF_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_MAN_DIR ${MAN_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_INFO_DIR ${INFO_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SERVICES_DIR ${SERVICES_INSTALL_DIR}) make_install_path_absolute(KDEPIMLIBS_SERVICETYPES_DIR ${SERVICETYPES_INSTALL_DIR}) # Used in configure_file() and install(EXPORT) set(KDEPIMLIBS_TARGET_PREFIX KDEPIMLibs__) # this file is installed and contains all necessary information about the installed kdepimlibs, it also loads the file with the exported targets configure_file(KdepimLibsConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfig.cmake" @ONLY) # this file will be installed too and will be used by cmake when searching for the Config.cmake file to check the version of kdepimlibs, Alex macro_write_basic_cmake_version_file(${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfigVersion.cmake ${KDEPIMLIBS_VERSION_MAJOR} ${KDEPIMLIBS_VERSION_MINOR} ${KDEPIMLIBS_VERSION_PATCH}) set(_KdepimLibsConfig_INSTALL_DIR ${LIB_INSTALL_DIR}/KdepimLibs/cmake) # places where find_package() looks for FooConfig.cmake files: # CMake >= 2.6.0 looks in lib/Foo*/cmake/, CMake >= 2.6.3 also looks in # lib/cmake/Foo*/, which packagers prefer. So they can set the KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR # option to have kdepimlibs install its Config file there. Alex if(KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR) set(_KdepimLibsConfig_INSTALL_DIR ${LIB_INSTALL_DIR}/cmake/KdepimLibs) endif(KDE4_USE_COMMON_CMAKE_PACKAGE_CONFIG_DIR) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/KdepimLibsConfig.cmake DESTINATION ${_KdepimLibsConfig_INSTALL_DIR} ) # Install the file with the exported targets, use ${KDEPIMLIBS_TARGET_PREFIX} as prefix for the names of these targets, Alex install(EXPORT kdepimlibsLibraryTargets NAMESPACE ${KDEPIMLIBS_TARGET_PREFIX} DESTINATION ${_KdepimLibsConfig_INSTALL_DIR} FILE KDEPimLibsLibraryTargetsWithPrefix.cmake ) # Install a KDEPimLibsDependencies.cmake so people using kdepimlibs 4.2 with kdelibs < 4.2 get a useful error message, Alex file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/KDEPimLibsDependencies.cmake "\n message(FATAL_ERROR \"For using this version of kdepimlibs (${KDEPIMLIBS_VERSION}) you need a newer version of kdelibs, please update.\")\n") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KDEPimLibsDependencies.cmake DESTINATION ${DATA_INSTALL_DIR}/cmake/modules) diff --git a/kcal/person.cpp b/kcal/person.cpp index b07eb4161..de1c35849 100644 --- a/kcal/person.cpp +++ b/kcal/person.cpp @@ -1,171 +1,176 @@ /* - This file is part of the kcal library. + This file is part of the kcal library. - Copyright (c) 2001 Cornelius Schumacher - Copyright (C) 2003-2004 Reinhold Kainhofer + Copyright (c) 2001 Cornelius Schumacher + Copyright (C) 2003-2004 Reinhold Kainhofer - 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 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. + 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. + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Person class. @brief Represents a person, by name and email address. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #include "person.h" #include "kpimutils/email.h" #include #include #include using namespace KCal; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::Person::Private { public: QString mName; // person name QString mEmail; // person email address }; //@endcond Person::Person() : d( new KCal::Person::Private ) { } Person::Person( const QString &fullName ) : d( new Private ) { KPIMUtils::extractEmailAddressAndName( fullName, d->mEmail, d->mName ); } Person Person::fromFullName( const QString &fullName ) { QString email, name; KPIMUtils::extractEmailAddressAndName( fullName, email, name ); return Person( name, email ); } Person::Person( const QString &name, const QString &email ) : d( new KCal::Person::Private ) { d->mName = name; d->mEmail = email; } Person::Person( const Person &person ) : d( new KCal::Person::Private( *person.d ) ) { } Person::~Person() { delete d; } #if defined(Q_CC_MSVC) bool KCal::Person::operator==( const Person &person ) const #else bool KCal::Person::operator==( const Person &person ) #endif { return d->mName == person.d->mName && d->mEmail == person.d->mEmail; } Person &KCal::Person::operator=( const Person &person ) { // check for self assignment if ( &person == this ) { return *this; } *d = *person.d; return *this; } QString Person::fullName() const { if ( d->mName.isEmpty() ) { return d->mEmail; } else { if ( d->mEmail.isEmpty() ) { return d->mName; } else { // Taken from KABC::Addressee::fullEmail QString name = d->mName; QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ); bool weNeedToQuote = name.indexOf( needQuotes ) != -1; if ( weNeedToQuote ) { if ( name[0] != '"' ) { name.prepend( '"' ); } if ( name[ name.length()-1 ] != '"' ) { name.append( '"' ); } } return name + " <" + d->mEmail + '>'; } } } QString Person::name() const { return d->mName; } QString Person::email() const { return d->mEmail; } bool Person::isEmpty() const { return d->mEmail.isEmpty() && d->mName.isEmpty(); } void Person::setName( const QString &name ) { d->mName = name; } void Person::setEmail( const QString &email ) { if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { d->mEmail = email.mid( 7 ); } else { d->mEmail = email; } } + +/* + Return a hash value for a Person argument. + @param key is a Person. +*/ static inline uint qHash( const Person &key ) { return qHash( key.fullName() ); } diff --git a/kcal/person.h b/kcal/person.h index aaea8c4a6..ecee07426 100644 --- a/kcal/person.h +++ b/kcal/person.h @@ -1,159 +1,163 @@ /* - This file is part of the kcal library. + This file is part of the kcal library. - Copyright (c) 2001-2003 Cornelius Schumacher - Copyright (C) 2003-2004 Reinhold Kainhofer + Copyright (c) 2001-2003 Cornelius Schumacher + Copyright (C) 2003-2004 Reinhold Kainhofer - 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 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. + 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. + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the Person class. - @brief - Represents a person, by name ane email address. - @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #ifndef KCAL_PERSON_H #define KCAL_PERSON_H #include #include #include "kcal_export.h" namespace KCal { /** + @brief + Represents a person, by name ane email address. + This class represents a person, with a name and an email address. - It supports the "FirstName LastName " format. + It supports the "FirstName LastName\ " format. */ class KCAL_EXPORT Person { public: /** Constructs a blank person. */ Person(); /** Constructs a person with name and email address taken from @p fullName. @param fullName is the name and email of the person in - the form FirstName LastName \. + the form "FirstName LastName \". */ static Person fromFullName( const QString &fullName ); /** - \deprecated - Use fromFullName() instead. + Constructs a person with name and email address taken from @p fullName. + + @param fullName is the name and email of the person in + the form "FirstName LastName \". + + @deprecated use fromFullName() instead. */ KDE_CONSTRUCTOR_DEPRECATED explicit Person( const QString &fullName ); /** Constructs a person with the name @p name and email address @p email. @param name is the name of this person. @param email is the email address of this person. */ Person( const QString &name, const QString &email ); /** Constructs a person as a copy of another person object. @param person is the person to copy. */ Person( const Person &person ); /** Destroys a person. */ ~Person(); /** Returns true if the person name and email address are empty. */ bool isEmpty() const; /** Returns the full name of this person. */ QString fullName( ) const; /** Sets the name of the person to @p name. @param name is the name of this person. @see name() */ void setName( const QString &name ); /** Returns the person name string. @see setName() */ QString name() const; /** Sets the email address for this person to @p email. @param email is the email address for this person. @see email() */ void setEmail( const QString &email ); /** Returns the email address for this person. @see setEmail() */ QString email() const; /** Compares this with @p person for equality. @param person is the person to compare. */ //KDE5: make const for all #if defined(Q_CC_MSVC) bool operator==( const Person &person ) const; #else bool operator==( const Person &person ); //krazy:exclude=operators #endif /** Sets this person equal to @p person. @param person is the person to copy. */ Person &operator=( const Person &person ); private: //@cond PRIVATE class Private; Private *const d; //@endcond }; } #endif diff --git a/kioslave/nntp/Mainpage.dox b/kioslave/nntp/Mainpage.dox new file mode 100644 index 000000000..57b7f2744 --- /dev/null +++ b/kioslave/nntp/Mainpage.dox @@ -0,0 +1 @@ +// DOXYGEN_REFERENCES = kdecore kdeui kio diff --git a/kioslave/nntp/nntp.cpp b/kioslave/nntp/nntp.cpp index 1ccf8fa24..d8bb1d605 100644 --- a/kioslave/nntp/nntp.cpp +++ b/kioslave/nntp/nntp.cpp @@ -1,964 +1,978 @@ /* This file is part of KDE Copyright (C) 2000 by Wolfram Diestel Copyright (C) 2005 by Tim Way Copyright (C) 2005 by Volker Krause This is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. */ #include "nntp.h" #include #include #include #include #include #include #include #include #include #include #include #define DBG_AREA 7114 #define DBG kDebug(DBG_AREA) #define ERR kError(DBG_AREA) using namespace KIO; extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } int kdemain(int argc, char **argv) { KComponentData componentData("kio_nntp"); if (argc != 4) { fprintf(stderr, "Usage: kio_nntp protocol domain-socket1 domain-socket2\n"); exit(-1); } NNTPProtocol *slave; // Are we going to use SSL? if (strcasecmp(argv[1], "nntps") == 0) { slave = new NNTPProtocol(argv[2], argv[3], true); } else { slave = new NNTPProtocol(argv[2], argv[3], false); } slave->dispatchLoop(); delete slave; return 0; } /****************** NNTPProtocol ************************/ NNTPProtocol::NNTPProtocol ( const QByteArray & pool, const QByteArray & app, bool isSSL ) : TCPSlaveBase((isSSL ? "nntps" : "nntp"), pool, app, isSSL ), isAuthenticated( false ) { DBG << "=============> NNTPProtocol::NNTPProtocol"; readBufferLen = 0; m_defaultPort = isSSL ? DEFAULT_NNTPS_PORT : DEFAULT_NNTP_PORT; m_port = m_defaultPort; } NNTPProtocol::~NNTPProtocol() { DBG << "<============= NNTPProtocol::~NNTPProtocol"; // close connection nntp_close(); } void NNTPProtocol::setHost ( const QString & host, quint16 port, const QString & user, const QString & pass ) { DBG << ( ! user.isEmpty() ? (user+'@') : QString("")) << host << ":" << ( ( port == 0 ) ? m_defaultPort : port ); if ( isConnected() && (mHost != host || m_port != port || mUser != user || mPass != pass) ) nntp_close(); mHost = host; m_port = ( ( port == 0 ) ? m_defaultPort : port ); mUser = user; mPass = pass; } void NNTPProtocol::get( const KUrl& url ) { DBG << url.prettyUrl(); QString path = QDir::cleanPath(url.path()); // path should be like: /group/ or /group/ if ( path.startsWith( QDir::separator() ) ) path.remove( 0, 1 ); int pos = path.indexOf( QDir::separator() ); QString group; QString msg_id; if ( pos > 0 ) { group = path.left( pos ); msg_id = path.mid( pos + 1 ); } if ( group.isEmpty() || msg_id.isEmpty() ) { error(ERR_DOES_NOT_EXIST,path); return; } int res_code; DBG << "group:" << group << "msg:" << msg_id; if ( !nntp_open() ) return; // select group if necessary if ( mCurrentGroup != group && !group.isEmpty() ) { infoMessage( i18n("Selecting group %1...", group ) ); res_code = sendCommand( "GROUP " + group ); if ( res_code == 411 ){ error( ERR_DOES_NOT_EXIST, path ); mCurrentGroup.clear(); return; } else if ( res_code != 211 ) { unexpected_response( res_code, "GROUP" ); mCurrentGroup.clear(); return; } mCurrentGroup = group; } // get article infoMessage( i18n("Downloading article...") ); res_code = sendCommand( "ARTICLE " + msg_id ); if ( res_code == 423 || res_code == 430 ) { error( ERR_DOES_NOT_EXIST, path ); return; } else if (res_code != 220) { unexpected_response(res_code,"ARTICLE"); return; } // read and send data char tmp[MAX_PACKET_LEN]; while ( true ) { if ( !waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } int len = readLine( tmp, MAX_PACKET_LEN ); const char* buffer = tmp; if ( len <= 0 ) break; if ( len == 3 && tmp[0] == '.' && tmp[1] == '\r' && tmp[2] == '\n') break; if ( len > 1 && tmp[0] == '.' && tmp[1] == '.' ) { ++buffer; --len; } data( QByteArray::fromRawData( buffer, len ) ); } // end of data data(QByteArray()); // finish finished(); } void NNTPProtocol::put( const KUrl &/*url*/, int /*permissions*/, KIO::JobFlags /*flags*/ ) { if ( !nntp_open() ) return; if ( post_article() ) finished(); } void NNTPProtocol::special(const QByteArray& data) { // 1 = post article int cmd; QDataStream stream(data); if ( !nntp_open() ) return; stream >> cmd; if (cmd == 1) { if (post_article()) finished(); } else { error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1", cmd)); } } bool NNTPProtocol::post_article() { DBG; // send post command infoMessage( i18n("Sending article...") ); int res_code = sendCommand( "POST" ); if (res_code == 440) { // posting not allowed error(ERR_WRITE_ACCESS_DENIED, mHost); return false; } else if (res_code != 340) { // 340: ok, send article unexpected_response(res_code,"POST"); return false; } // send article now int result; bool last_chunk_had_line_ending = true; do { QByteArray buffer; dataReq(); result = readData( buffer ); DBG << "receiving data:" << buffer; // treat the buffer data if ( result > 0 ) { // translate "\r\n." to "\r\n.." int pos = 0; if ( last_chunk_had_line_ending && buffer[0] == '.' ) { buffer.insert( 0, '.' ); pos += 2; } last_chunk_had_line_ending = ( buffer.endsWith( "\r\n" ) ); //krazy:exclude=strings while ( (pos = buffer.indexOf( "\r\n.", pos )) > 0) { buffer.insert( pos + 2, '.' ); pos += 4; } // send data to socket, write() doesn't send the terminating 0 write( buffer, buffer.length() ); DBG << "writing:" << buffer; } } while ( result > 0 ); // error occurred? if (result<0) { ERR << "error while getting article data for posting"; nntp_close(); return false; } // send end mark write( "\r\n.\r\n", 5 ); // get answer res_code = evalResponse( readBuffer, readBufferLen ); if (res_code == 441) { // posting failed error(ERR_COULD_NOT_WRITE, mHost); return false; } else if (res_code != 240) { unexpected_response(res_code,"POST"); return false; } return true; } void NNTPProtocol::stat( const KUrl& url ) { DBG << url.prettyUrl(); UDSEntry entry; QString path = QDir::cleanPath(url.path()); QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",Qt::CaseInsensitive); QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", Qt::CaseInsensitive); int pos; QString group; QString msg_id; // / = group list if (path.isEmpty() || path == "/") { DBG << "root"; fillUDSEntry( entry, QString(), 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) ); // /group = message list } else if (regGroup.indexIn(path) == 0) { if ( path.startsWith( '/' ) ) path.remove(0,1); if ((pos = path.indexOf('/')) > 0) group = path.left(pos); else group = path; DBG << "group:" << group; // postingAllowed should be ored here with "group not moderated" flag // as size the num of messages (GROUP cmd) could be given fillUDSEntry( entry, group, 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) ); // /group/ = message } else if (regMsgId.indexIn(path) == 0) { pos = path.indexOf('<'); group = path.left(pos); msg_id = KUrl::fromPercentEncoding( path.right(path.length()-pos).toLatin1() ); if ( group.startsWith( '/' ) ) group.remove( 0, 1 ); if ((pos = group.indexOf('/')) > 0) group = group.left(pos); DBG << "group:" << group << "msg:" << msg_id; fillUDSEntry( entry, msg_id, 0, true ); // invalid url } else { error(ERR_DOES_NOT_EXIST,path); return; } statEntry(entry); finished(); } void NNTPProtocol::listDir( const KUrl& url ) { DBG << url.prettyUrl(); if ( !nntp_open() ) return; QString path = QDir::cleanPath(url.path()); if (path.isEmpty()) { KUrl newURL(url); newURL.setPath("/"); DBG << "redirecting to" << newURL.prettyUrl(); redirection(newURL); finished(); return; } else if ( path == "/" ) { fetchGroups( url.queryItem( "since" ), url.queryItem( "desc" ) == "true" ); finished(); } else { // if path = /group int pos; QString group; if ( path.startsWith( '/' ) ) path.remove( 0, 1 ); if ((pos = path.indexOf('/')) > 0) group = path.left(pos); else group = path; QString first = url.queryItem( "first" ); QString max = url.queryItem( "max" ); if ( fetchGroup( group, first.toULong(), max.toULong() ) ) finished(); } } void NNTPProtocol::fetchGroups( const QString &since, bool desc ) { int expected; int res; if ( since.isEmpty() ) { // full listing infoMessage( i18n("Downloading group list...") ); res = sendCommand( "LIST" ); expected = 215; } else { // incremental listing infoMessage( i18n("Looking for new groups...") ); res = sendCommand( "NEWGROUPS " + since ); expected = 231; } if ( res != expected ) { unexpected_response( res, "LIST" ); return; } // read newsgroups line by line QByteArray line; QString group; int pos, pos2; long msg_cnt; long access; UDSEntry entry; QHash entryMap; // read in data and process each group. one line at a time while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QByteArray( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; // group name if ((pos = line.indexOf(' ')) > 0) { group = line.left(pos); // number of messages line.remove(0,pos+1); long last = 0; access = 0; if (((pos = line.indexOf(' ')) > 0 || (pos = line.indexOf('\t')) > 0) && ((pos2 = line.indexOf(' ',pos+1)) > 0 || (pos2 = line.indexOf('\t',pos+1)) > 0)) { last = line.left(pos).toLongLong(); long first = line.mid(pos+1,pos2-pos-1).toLongLong(); msg_cnt = abs(last-first+1); // group access rights switch ( line[pos2 + 1] ) { case 'n': access = 0; break; case 'm': access = S_IWUSR | S_IWGRP; break; case 'y': access = S_IWUSR | S_IWGRP | S_IWOTH; break; } } else { msg_cnt = 0; } entry.clear(); fillUDSEntry( entry, group, msg_cnt, false, access ); if ( !desc ) listEntry( entry, false ); else entryMap.insert( group, entry ); } } // handle group descriptions QHash::Iterator it = entryMap.begin(); if ( desc ) { infoMessage( i18n("Downloading group descriptions...") ); totalSize( entryMap.size() ); } while ( desc ) { // request all group descriptions if ( since.isEmpty() ) res = sendCommand( "LIST NEWSGROUPS" ); else { // request only descriptions for new groups if ( it == entryMap.end() ) break; res = sendCommand( "LIST NEWSGROUPS " + it.key() ); ++it; if( res == 503 ) { // Information not available (RFC 2980 §2.1.6), try next group continue; } } if ( res != 215 ) { // No group description available or not implemented break; } // download group descriptions while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QByteArray( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; //DBG << " fetching group description: " << QString( line ).trimmed(); int pos = line.indexOf( ' ' ); pos = pos < 0 ? line.indexOf( '\t' ) : qMin( pos, line.indexOf( '\t' ) ); group = line.left( pos ); QString groupDesc = line.right( line.length() - pos ).trimmed(); if ( entryMap.contains( group ) ) { entry = entryMap.take( group ); entry.insert( KIO::UDSEntry::UDS_EXTRA, groupDesc ); listEntry( entry, false ); } } if ( since.isEmpty() ) break; } // take care of groups without descriptions for ( QHash::Iterator it = entryMap.begin(); it != entryMap.end(); ++it ) listEntry( it.value(), false ); entry.clear(); listEntry( entry, true ); } bool NNTPProtocol::fetchGroup( QString &group, unsigned long first, unsigned long max ) { int res_code; QString resp_line; // select group infoMessage( i18n("Selecting group %1...", group ) ); res_code = sendCommand( "GROUP " + group ); if ( res_code == 411 ) { error( ERR_DOES_NOT_EXIST, group ); mCurrentGroup.clear(); return false; } else if ( res_code != 211 ) { unexpected_response( res_code, "GROUP" ); mCurrentGroup.clear(); return false; } mCurrentGroup = group; // repsonse to "GROUP " command is 211 then find the message count (cnt) // and the first and last message followed by the group name unsigned long firstSerNum, lastSerNum; resp_line = QString::fromLatin1( readBuffer ); QRegExp re ( "211\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); if ( re.indexIn( resp_line ) != -1 ) { firstSerNum = re.cap( 2 ).toLong(); lastSerNum = re.cap( 3 ).toLong(); } else { error( ERR_INTERNAL, i18n("Could not extract message serial numbers from server response:\n%1", resp_line ) ); return false; } if (firstSerNum == 0) return true; first = qMax( first, firstSerNum ); if ( max > 0 && lastSerNum - first > max ) first = lastSerNum - max + 1; DBG << "Starting from serial number: " << first << " of " << firstSerNum << " - " << lastSerNum; setMetaData( "FirstSerialNumber", QString::number( firstSerNum ) ); setMetaData( "LastSerialNumber", QString::number( lastSerNum ) ); infoMessage( i18n("Downloading new headers...") ); totalSize( lastSerNum - first ); bool notSupported = true; if ( fetchGroupXOVER( first, notSupported ) ) return true; else if ( notSupported ) return fetchGroupRFC977( first ); return false; } bool NNTPProtocol::fetchGroupRFC977( unsigned long first ) { UDSEntry entry; // set article pointer to first article and get msg-id of it int res_code = sendCommand( "STAT " + QString::number( first ) ); QString resp_line = readBuffer; if (res_code != 223) { unexpected_response(res_code,"STAT"); return false; } //STAT res_line: 223 nnn ... QString msg_id; int pos, pos2; if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) { msg_id = resp_line.mid(pos,pos2-pos+1); fillUDSEntry( entry, msg_id, 0, true ); listEntry( entry, false ); } else { error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1", resp_line)); return false; } // go through all articles while (true) { res_code = sendCommand("NEXT"); if (res_code == 421) { // last artice reached entry.clear(); listEntry( entry, true ); return true; } else if (res_code != 223) { unexpected_response(res_code,"NEXT"); return false; } //res_line: 223 nnn ... resp_line = readBuffer; if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) { msg_id = resp_line.mid(pos,pos2-pos+1); entry.clear(); fillUDSEntry( entry, msg_id, 0, true ); listEntry( entry, false ); } else { error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1", resp_line)); return false; } } return true; // Not reached } bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool ¬Supported ) { notSupported = false; QString line; QStringList headers; int res = sendCommand( "LIST OVERVIEW.FMT" ); if ( res == 215 ) { while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return false; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QString::fromLatin1( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; headers << line.trimmed(); DBG << "OVERVIEW.FMT:" << line.trimmed(); } } else { // fallback to defaults headers << "Subject:" << "From:" << "Date:" << "Message-ID:" << "References:" << "Bytes:" << "Lines:"; } res = sendCommand( "XOVER " + QString::number( first ) + '-' ); if ( res == 420 ) return true; // no articles selected if ( res == 500 ) notSupported = true; // unknwon command if ( res != 224 ) return false; long msgSize; QString name; UDSEntry entry; int udsType; QStringList fields; while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return false; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QString::fromLatin1( readBuffer, readBufferLen ); if ( line == ".\r\n" ) { entry.clear(); listEntry( entry, true ); return true; } fields = line.split( '\t', QString::KeepEmptyParts); msgSize = 0; entry.clear(); udsType = KIO::UDSEntry::UDS_EXTRA; QStringList::ConstIterator it = headers.constBegin(); QStringList::ConstIterator it2 = fields.constBegin(); // first entry is the serial number name = (*it2); ++it2; for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) { if ( (*it) == "Bytes:" ) { msgSize = (*it2).toLong(); continue; } QString atomStr; if ( (*it).endsWith( QLatin1String( "full" ) ) ) if ( (*it2).trimmed().isEmpty() ) atomStr = (*it).left( (*it).indexOf( ':' ) + 1 ); // strip of the 'full' suffix else atomStr = (*it2).trimmed(); else atomStr = (*it) + ' ' + (*it2).trimmed(); entry.insert( udsType++, atomStr ); if ( udsType >= KIO::UDSEntry::UDS_EXTRA_END ) break; } fillUDSEntry( entry, name, msgSize, true ); listEntry( entry, false ); } return true; // not reached } void NNTPProtocol::fillUDSEntry( UDSEntry& entry, const QString& name, long size, bool is_article, long access ) { long posting=0; // entry name entry.insert(KIO::UDSEntry::UDS_NAME, name); // entry size entry.insert(KIO::UDSEntry::UDS_SIZE, size); // file type entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, is_article? S_IFREG : S_IFDIR); // access permissions posting = postingAllowed? access : 0; long long accessVal = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting); entry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal); entry.insert(KIO::UDSEntry::UDS_USER, mUser.isEmpty() ? QString::fromLatin1("root") : mUser); /* entry->insert(UDS_GROUP, QString::fromLatin1("root")); */ // MIME type if (is_article) { entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/news") ); } } void NNTPProtocol::nntp_close () { if ( isConnected() ) { write( "QUIT\r\n", 6 ); disconnectFromHost(); isAuthenticated = false; } mCurrentGroup.clear(); } bool NNTPProtocol::nntp_open() { // if still connected reuse connection if ( isConnected() ) { DBG << "reusing old connection"; return true; } DBG << " nntp_open -- creating a new connection to" << mHost << ":" << m_port; // create a new connection (connectToHost() includes error handling) infoMessage( i18n("Connecting to server...") ); - if ( connectToHost( (isAutoSsl() ? "nntps" : "nntp"), mHost.toLatin1(), m_port ) ) + if ( connectToHost( (isAutoSsl() ? "nntps" : "nntp"), mHost, m_port ) ) { DBG << " nntp_open -- connection is open"; // read greeting int res_code = evalResponse( readBuffer, readBufferLen ); /* expect one of 200 server ready - posting allowed 201 server ready - no posting allowed */ if ( ! ( res_code == 200 || res_code == 201 ) ) { unexpected_response(res_code,"CONNECT"); return false; } DBG << " nntp_open -- greating was read res_code :" << res_code; res_code = sendCommand("MODE READER"); // TODO: not in RFC 977, so we should not abort here if ( !(res_code == 200 || res_code == 201) ) { unexpected_response( res_code, "MODE READER" ); return false; } // let local class know whether posting is allowed or not postingAllowed = (res_code == 200); // activate TLS if requested if ( metaData("tls") == "on" ) { if ( sendCommand( "STARTTLS" ) != 382 ) { error( ERR_COULD_NOT_CONNECT, i18n("This server does not support TLS") ); return false; } if ( !startSsl() ) { error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") ); return false; } } // *try* to authenticate now (see bug#167718) authenticate(); return true; } return false; } int NNTPProtocol::sendCommand( const QString &cmd ) { int res_code = 0; if ( !nntp_open() ) { ERR << "NOT CONNECTED, cannot send cmd" << cmd; return 0; } DBG << "cmd:" << cmd; write( cmd.toLatin1(), cmd.length() ); // check the command for proper termination if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) ) write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); // if authorization needed send user info if (res_code == 480) { DBG << "auth needed, sending user info"; if ( mUser.isEmpty() || mPass.isEmpty() ) { KIO::AuthInfo authInfo; authInfo.username = mUser; authInfo.password = mPass; if ( openPasswordDialog( authInfo ) ) { mUser = authInfo.username; mPass = authInfo.password; } } if ( mUser.isEmpty() || mPass.isEmpty() ) return res_code; res_code = authenticate(); if (res_code != 281) { // error should be handled by invoking function return res_code; } // ok now, resend command write( cmd.toLatin1(), cmd.length() ); if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) ) write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); } return res_code; } int NNTPProtocol::authenticate() { int res_code = 0; if( isAuthenticated ) { // already authenticated return 281; } if( mUser.isEmpty() || mPass.isEmpty() ) { return 281; // failsafe : maybe add a "relax" mode to optionally ask user/pwd. } // send username to server and confirm response write( "AUTHINFO USER ", 14 ); write( mUser.toLatin1(), mUser.length() ); write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); if( res_code == 281 ) { // no password needed (RFC 2980 3.1.1 does not required one) return res_code; } if (res_code != 381) { // error should be handled by invoking function return res_code; } // send password write( "AUTHINFO PASS ", 14 ); write( mPass.toLatin1(), mPass.length() ); write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); if( res_code == 281 ) { isAuthenticated = true; } return res_code; } void NNTPProtocol::unexpected_response( int res_code, const QString &command ) { ERR << "Unexpected response to" << command << "command: (" << res_code << ")" << readBuffer; - KIO::Error errCode; + // See RFC 3977 appendix C "Summary of Response Codes" switch ( res_code ) { - case 480: errCode = ERR_COULD_NOT_LOGIN; break; - default: errCode = ERR_INTERNAL; + case 205: // connection closed by the server: this can happens, e.g. if the session timeout on the server side + // Not the same thing, but use the same message as code 400 anyway. + case 400: // temporary issue on the server + error( ERR_INTERNAL_SERVER, + i18n( "The server %1 could not handle your request.\n" + "Please try again now or latter if the problem persists.", mHost ) ); + break; + case 480: // credential request + error( ERR_COULD_NOT_LOGIN, + i18n( "You need to authenticate to access the requested resource." ) ); + case 481: // wrong credential (TODO: place a specific message for this case) + error( ERR_COULD_NOT_LOGIN, + i18n( "The supplied login and/or password are incorrect." ) ); + break; + case 502: + error( ERR_ACCESS_DENIED, mHost ); + break; + default: + error( ERR_INTERNAL, i18n( "Unexpected server response to %1 command:\n%2", command, readBuffer ) ); } - error( errCode, i18n("Unexpected server response to %1 command:\n%2", - command, readBuffer ) ); - nntp_close(); } int NNTPProtocol::evalResponse ( char *data, ssize_t &len ) { if ( !waitForResponse( responseTimeout() ) ) { error( ERR_SERVER_TIMEOUT , mHost ); nntp_close(); return -1; } len = readLine( data, MAX_PACKET_LEN ); if ( len < 3 ) return -1; // get the first three characters. should be the response code int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) ); DBG << "got:" << respCode; return respCode; } /* not really necessary, because the slave has to use the KIO::Error's instead, but let this here for documentation of the NNTP response codes and may by later use. QString& NNTPProtocol::errorStr(int resp_code) { QString ret; switch (resp_code) { case 100: ret = "help text follows"; break; case 199: ret = "debug output"; break; case 200: ret = "server ready - posting allowed"; break; case 201: ret = "server ready - no posting allowed"; break; case 202: ret = "slave status noted"; break; case 205: ret = "closing connection - goodbye!"; break; case 211: ret = "group selected"; break; case 215: ret = "list of newsgroups follows"; break; case 220: ret = "article retrieved - head and body follow"; break; case 221: ret = "article retrieved - head follows"; break; case 222: ret = "article retrieved - body follows"; break; case 223: ret = "article retrieved - request text separately"; break; case 230: ret = "list of new articles by message-id follows"; break; case 231: ret = "list of new newsgroups follows"; break; case 235: ret = "article transferred ok"; break; case 240: ret = "article posted ok"; break; case 335: ret = "send article to be transferred"; break; case 340: ret = "send article to be posted"; break; case 400: ret = "service discontinued"; break; case 411: ret = "no such news group"; break; case 412: ret = "no newsgroup has been selected"; break; case 420: ret = "no current article has been selected"; break; case 421: ret = "no next article in this group"; break; case 422: ret = "no previous article in this group"; break; case 423: ret = "no such article number in this group"; break; case 430: ret = "no such article found"; break; case 435: ret = "article not wanted - do not send it"; break; case 436: ret = "transfer failed - try again later"; break; case 437: ret = "article rejected - do not try again"; break; case 440: ret = "posting not allowed"; break; case 441: ret = "posting failed"; break; case 500: ret = "command not recognized"; break; case 501: ret = "command syntax error"; break; case 502: ret = "access restriction or permission denied"; break; case 503: ret = "program fault - command not performed"; break; default: ret = QString("unknown NNTP response code %1").arg(resp_code); } return ret; } */ diff --git a/kioslave/nntp/nntp.h b/kioslave/nntp/nntp.h index 46849c866..d24591807 100644 --- a/kioslave/nntp/nntp.h +++ b/kioslave/nntp/nntp.h @@ -1,141 +1,149 @@ /* This file is part of KDE Copyright (C) 2000 by Wolfram Diestel Copyright (C) 2005 by Tim Way Copyright (C) 2005 by Volker Krause This is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. */ #ifndef _NNTP_H #define _NNTP_H #include #include #define MAX_PACKET_LEN 8192 /* TODO: - test special post command - progress information in get, and maybe post - remove unnecessary debug stuff */ - +/** + @brief NNTP KIO slave + + @par References + - RFC 850: Standard for interchange of USENET messages + - RFC 2980: Common NNTP Extensions (updated by RFC 3977) + - RFC 3977: Network News Transfer Protocol + - RFC 4643: Network News Transfer Protocol (NNTP) Extension for Authentication +*/ class NNTPProtocol:public KIO::TCPSlaveBase { public: /** Default Constructor * @param isSSL is a true or false to indicate whether ssl is to be used */ NNTPProtocol ( const QByteArray & pool, const QByteArray & app, bool isSSL ); virtual ~NNTPProtocol(); virtual void get(const KUrl& url ); virtual void put( const KUrl& url, int permissions, KIO::JobFlags flags ); virtual void stat(const KUrl& url ); virtual void listDir(const KUrl& url ); virtual void setHost(const QString& host, quint16 port, const QString& user, const QString& pass); /** * Special command: 1 = post article * it takes no other args, the article data are * requested by dataReq() and should be valid * as in RFC850. It's not checked for correctness here. * @deprecated use put() for posting */ virtual void special(const QByteArray& data); protected: /** * Send a command to the server. Returns the response code and * the response line */ int sendCommand( const QString &cmd ); /** * Attempt to properly shut down the NNTP connection by sending * "QUIT\r\n" before closing the socket. */ void nntp_close (); /** * Attempt to initiate a NNTP connection via a TCP socket, if no existing * connection could be reused. */ bool nntp_open(); /** * Post article. Invoked by special() and put() */ bool post_article(); private: QString mHost, mUser, mPass; quint16 m_port, m_defaultPort; bool postingAllowed, isAuthenticated; char readBuffer[MAX_PACKET_LEN]; ssize_t readBufferLen; /// Current selected newsgroup QString mCurrentGroup; /** * Fetch all new groups since the given date or (if the date is empty) * all available groups. * @param since Date as specified in RFC 977 for the NEWGROUPS command * @param desc Fetch group description (needs more memory) */ void fetchGroups( const QString &since, bool desc ); /** * Fetch message listing from the given newsgroup. * This will use RFC2980 XOVER if available, plain RFC977 STAT/NEXT * otherwise. * @param group The newsgroup name * @param first Serial number of the first message, 0 lists all messages. * @param max Maximal number of returned messages, 0 means unlimited. * @return true on success, false otherwise. */ bool fetchGroup ( QString &group, unsigned long first = 0, unsigned long max = 0 ); /** * Fetch message listing from the current group using RFC977 STAT/NEXT * commands. * @param first message number of the first article * @return true on success, false otherwise. */ bool fetchGroupRFC977( unsigned long first ); /** * Fetch message listing from the current group using the RFC2980 XOVER * command. * Additional headers provided by XOVER are added as UDS_EXTRA entries * to the listing. * @param first message number of the first article * @param notSupported boolean reference to indicate if command failed * due to missing XOVER support on the server. * @return true on success, false otherwise */ bool fetchGroupXOVER( unsigned long first, bool ¬Supported ); /// creates an UDSEntry with file information used in stat and listDir void fillUDSEntry ( KIO::UDSEntry & entry, const QString & name, long size, bool is_article, long access = 0 ); /// error handling for unexpected responses void unexpected_response ( int res_code, const QString & command ); /** * grabs the response line from the server. used after most send_cmd calls. max * length for the returned string ( char *data ) is 4096 characters including * the "\r\n" terminator. */ int evalResponse ( char *data, ssize_t &len ); /** * Try to authenticate to the server. * @return the response code from the server if the mUser/mPassword * are available; 281 (successful authentication) otherwise. */ int authenticate(); }; #endif diff --git a/kmime/tests/headertest.cpp b/kmime/tests/headertest.cpp index 4a15177fc..4fc88071b 100644 --- a/kmime/tests/headertest.cpp +++ b/kmime/tests/headertest.cpp @@ -1,758 +1,766 @@ /* Copyright (c) 2006 Volker Krause 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 "headertest.h" #include #include #include using namespace KMime; using namespace KMime::Headers; using namespace KMime::Headers::Generics; // the following test cases are taken from KDE mailinglists, bug reports, RFC 2045, // RFC 2183 and RFC 2822, Appendix A QTEST_KDEMAIN( HeaderTest, NoGUI ) void HeaderTest::testIdentHeader() { // empty header Headers::Generics::Ident* h = new Headers::Generics::Ident(); QVERIFY( h->isEmpty() ); // parse single identifier h->from7BitString( QByteArray( "<1162746587.784559.5038.nullmailer@svn.kde.org>" ) ); QCOMPARE( h->identifiers().count(), 1 ); QCOMPARE( h->identifiers().first(), QByteArray( "1162746587.784559.5038.nullmailer@svn.kde.org" ) ); QCOMPARE( h->asUnicodeString(), QString("<1162746587.784559.5038.nullmailer@svn.kde.org>") ); QVERIFY( !h->isEmpty() ); // clearing a header h->clear(); QVERIFY( h->isEmpty() ); QVERIFY( h->identifiers().isEmpty() ); delete h; // parse multiple identifiers h = new Headers::Generics::Ident(); h->from7BitString( QByteArray( "<1234@local.machine.example> <3456@example.net>" ) ); QCOMPARE( h->identifiers().count(), 2 ); QList ids = h->identifiers(); QCOMPARE( ids.takeFirst(), QByteArray( "1234@local.machine.example" ) ); QCOMPARE( ids.first(), QByteArray( "3456@example.net" ) ); delete h; // parse multiple identifiers with folded headers h = new Headers::Generics::Ident(); h->from7BitString( QByteArray( "<1234@local.machine.example>\n <3456@example.net>" ) ); QCOMPARE( h->identifiers().count(), 2 ); ids = h->identifiers(); QCOMPARE( ids.takeFirst(), QByteArray( "1234@local.machine.example" ) ); QCOMPARE( ids.first(), QByteArray( "3456@example.net" ) ); // appending of new identifiers (with and without angle-brackets) h->appendIdentifier( "" ); h->appendIdentifier( "78910@example.net" ); QCOMPARE( h->identifiers().count(), 4 ); // assemble the final header QCOMPARE( h->as7BitString( false ), QByteArray("<1234@local.machine.example> <3456@example.net> <78910@example.net>") ); } void HeaderTest::testAddressListHeader() { // empty header Headers::Generics::AddressList *h = new Headers::Generics::AddressList(); QVERIFY( h->isEmpty() ); // parse single simple address h->from7BitString( "joe@where.test" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("joe@where.test") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString() ); QCOMPARE( h->prettyAddresses().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("joe@where.test") ); // clearing a header h->clear(); QVERIFY( h->isEmpty() ); delete h; // parsing and re-assembling a single address with display name h = new Headers::Generics::AddressList(); h->from7BitString( "Pete " ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "pete@silly.example" ) ); QCOMPARE( h->displayNames().first(), QString("Pete") ); QCOMPARE( h->prettyAddresses().first(), QString("Pete ") ); QCOMPARE( h->as7BitString( false ), QByteArray("Pete ") ); delete h; // parsing a single address with legacy comment style display name h = new Headers::Generics::AddressList(); h->from7BitString( "jdoe@machine.example (John Doe)" ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "jdoe@machine.example" ) ); QCOMPARE( h->displayNames().first(), QString("John Doe") ); QCOMPARE( h->prettyAddresses().first(), QString("John Doe ") ); delete h; // parsing and re-assembling list of diffrent addresses h = new Headers::Generics::AddressList(); h->from7BitString( "Mary Smith , jdoe@example.org, Who? " ); QCOMPARE( h->addresses().count(), 3 ); QStringList names = h->displayNames(); QCOMPARE( names.takeFirst(), QString("Mary Smith") ); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString("Who?") ); QCOMPARE( h->as7BitString( false ), QByteArray("Mary Smith , jdoe@example.org, Who? ") ); delete h; // same again with some interessting quoting h = new Headers::Generics::AddressList(); h->from7BitString( "\"Joe Q. Public\" , , \"Giant; \\\"Big\\\" Box\" " ); QCOMPARE( h->addresses().count(), 3 ); names = h->displayNames(); QCOMPARE( names.takeFirst(), QString("Joe Q. Public") ); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString("Giant; \"Big\" Box") ); QCOMPARE( h->as7BitString( false ), QByteArray("\"Joe Q. Public\" , boss@nil.test, \"Giant; \\\"Big\\\" Box\" ") ); delete h; // a display name with non-latin1 content h = new Headers::Generics::AddressList(); h->from7BitString( "Ingo =?iso-8859-15?q?Kl=F6cker?= " ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "kloecker@kde.org" ) ); QCOMPARE( h->displayNames().first(), QString::fromUtf8("Ingo Klöcker") ); QCOMPARE( h->asUnicodeString(), QString::fromUtf8("Ingo Klöcker ") ); QCOMPARE( h->as7BitString( false ), QByteArray("Ingo =?ISO-8859-1?Q?Kl=F6cker?= ") ); delete h; // again, this time legacy style h = new Headers::Generics::AddressList(); h->from7BitString( "kloecker@kde.org (Ingo =?iso-8859-15?q?Kl=F6cker?=)" ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "kloecker@kde.org" ) ); QCOMPARE( h->displayNames().first(), QString::fromUtf8("Ingo Klöcker") ); delete h; // parsing a empty group h = new Headers::Generics::AddressList(); h->from7BitString( "Undisclosed recipients:;" ); QCOMPARE( h->addresses().count(), 0 ); delete h; // parsing and re-assembling a address list with a group h = new Headers::Generics::AddressList(); h->from7BitString( "A Group:Chris Jones ,joe@where.test,John ;" ); QCOMPARE( h->addresses().count(), 3 ); names = h->displayNames(); QCOMPARE( names.takeFirst(), QString("Chris Jones") ); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString("John") ); QCOMPARE( h->as7BitString( false ), QByteArray("Chris Jones , joe@where.test, John ") ); delete h; // modifying a header h = new Headers::Generics::AddressList(); h->from7BitString( "John " ); h->addAddress( "", QString::fromUtf8("Ingo Klöcker") ); h->addAddress( "c@a.test" ); QCOMPARE( h->addresses().count(), 3 ); QCOMPARE( h->asUnicodeString(), QString::fromUtf8("John , Ingo Klöcker , c@a.test") ); QCOMPARE( h->as7BitString( false ), QByteArray("John , Ingo =?ISO-8859-1?Q?Kl=F6cker?= , c@a.test") ); delete h; // parsing from utf-8 h = new Headers::Generics::AddressList(); h->fromUnicodeString( QString::fromUtf8("Ingo Klöcker "), "utf-8" ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "kloecker@kde.org" ) ); QCOMPARE( h->displayNames().first(), QString::fromUtf8("Ingo Klöcker") ); delete h; // based on bug #137033, a header broken in various ways: ';' as list separator, // unquoted '.' in display name h = new Headers::Generics::AddressList(); h->from7BitString( "Vice@censored.serverkompetenz.net,\n President@mail2.censored.net;\"Int\\\\\\\\\\\\\\\\\\\\'l\" Lotto Commission. " ); QCOMPARE( h->addresses().count(), 3 ); names = h->displayNames(); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString() ); // there is an wrong ' ' after the name, but since the header is completely // broken we can be happy it parses at all... QCOMPARE( names.takeFirst(), QString("Int\\\\\\\\\\'l Lotto Commission. ") ); QList addrs = h->addresses(); QCOMPARE( addrs.takeFirst(), QByteArray("Vice@censored.serverkompetenz.net") ); QCOMPARE( addrs.takeFirst(), QByteArray("President@mail2.censored.net") ); QCOMPARE( addrs.takeFirst(), QByteArray("censored@yahoo.fr") ); delete h; // based on bug #102010, a display name containing '<' h = new Headers::Generics::AddressList( 0, QByteArray("\"|") ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("censored@censored.dy") ); QCOMPARE( h->displayNames().first(), QString("|as7BitString( false ), QByteArray("\"|") ); // based on bug #93790 (legacy display name with nested comments) h = new Headers::Generics::AddressList( 0, QByteArray("first.name@domain.tld (first name (nickname))") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString("first name (nickname)") ); QCOMPARE( h->as7BitString( false ), QByteArray("\"first name (nickname)\" ") ); delete h; // rfc 2047 encoding in quoted name (which is not allowed there) h = new Headers::Generics::AddressList(); h->from7BitString( QByteArray( "\"Ingo =?iso-8859-15?q?Kl=F6cker?=\" " ) ); QCOMPARE( h->mailboxes().count(), 1 ); QCOMPARE( h->asUnicodeString(), QString::fromUtf8( "Ingo =?iso-8859-15?q?Kl=F6cker?= " ) ); delete h; } void HeaderTest::testMailboxListHeader() { // empty header Headers::Generics::MailboxList *h = new Headers::Generics::MailboxList(); QVERIFY( h->isEmpty() ); // parse single simple address h->from7BitString( "joe_smith@where.test" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->mailboxes().count(), 1 ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("joe_smith@where.test") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString() ); QCOMPARE( h->prettyAddresses().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("joe_smith@where.test") ); // https://bugzilla.novell.com/show_bug.cgi?id=421057 (but apparently this was not the cause of the bug) h->from7BitString( "fr...@ce.sco (Francesco)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->mailboxes().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("Francesco ") ); delete h; } void HeaderTest::testSingleMailboxHeader() { // empty header Headers::Generics::SingleMailbox *h = new Headers::Generics::SingleMailbox(); QVERIFY( h->isEmpty() ); // parse single simple address h->from7BitString( "joe_smith@where.test" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("joe_smith@where.test") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString() ); QCOMPARE( h->prettyAddresses().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("joe_smith@where.test") ); delete h; } void HeaderTest::testMailCopiesToHeader() { Headers::MailCopiesTo *h; // empty header h = new Headers::MailCopiesTo(); QVERIFY( h->isEmpty() ); QVERIFY( !h->alwaysCopy() ); QVERIFY( !h->neverCopy() ); // set to always copy to poster h->setAlwaysCopy(); QVERIFY( !h->isEmpty() ); QVERIFY( h->alwaysCopy() ); QVERIFY( !h->neverCopy() ); QCOMPARE( h->as7BitString(), QByteArray( "Mail-Copies-To: poster" ) ); // set to never copy h->setNeverCopy(); QVERIFY( !h->isEmpty() ); QVERIFY( !h->alwaysCopy() ); QVERIFY( h->neverCopy() ); QCOMPARE( h->as7BitString(), QByteArray( "Mail-Copies-To: nobody" ) ); // clear header h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse copy to poster h = new MailCopiesTo( 0, "always" ); QVERIFY( h->addresses().isEmpty() ); QVERIFY( !h->isEmpty() ); QVERIFY( h->alwaysCopy() ); delete h; // parse never copy h = new MailCopiesTo( 0, "never" ); QVERIFY( h->addresses().isEmpty() ); QVERIFY( !h->isEmpty() ); QVERIFY( h->neverCopy() ); delete h; // parse address h = new MailCopiesTo( 0, "vkrause@kde.org" ); QVERIFY( !h->addresses().isEmpty() ); QVERIFY( h->alwaysCopy() ); QVERIFY( !h->neverCopy() ); QCOMPARE( h->as7BitString(), QByteArray( "Mail-Copies-To: vkrause@kde.org" ) ); delete h; } void HeaderTest::testParametrizedHeader() { Parametrized *h; // empty header h = new Parametrized(); QVERIFY( h->isEmpty() ); // add a parameter h->setParameter( "filename", "bla.jpg" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->parameter( "filename" ), QString( "bla.jpg" ) ); QCOMPARE( h->as7BitString( false ), QByteArray( "filename=\"bla.jpg\"" ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a parameter list h = new Parametrized( 0, "filename=genome.jpeg;\n modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" ); QCOMPARE( h->parameter( "filename" ), QString( "genome.jpeg" ) ); QCOMPARE( h->parameter( "modification-date" ), QString( "Wed, 12 Feb 1997 16:29:51 -0500" ) ); QCOMPARE( h->as7BitString( false ), QByteArray( "filename=\"genome.jpeg\"; modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" ) ); delete h; // quoting of whitespaces in parameter value h = new Parametrized(); h->setParameter( "boundary", "simple boundary" ); QCOMPARE( h->as7BitString( false ), QByteArray( "boundary=\"simple boundary\"" ) ); delete h; // TODO: test RFC 2047 encoded values // TODO: test case-insensitive key-names } void HeaderTest::testContentDispositionHeader() { ContentDisposition *h; // empty header h = new ContentDisposition(); QVERIFY( h->isEmpty() ); // set some values h->setFilename( "test.jpg" ); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString( false ).isEmpty() ); h->setDisposition( CDattachment ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( false ), QByteArray( "attachment; filename=\"test.jpg\"" ) ); delete h; // parse parameter-less header h = new ContentDisposition( 0, "inline" ); QCOMPARE( h->disposition(), CDinline ); QVERIFY( h->filename().isEmpty() ); QCOMPARE( h->as7BitString( true ), QByteArray( "Content-Disposition: inline" ) ); delete h; // parse header with parameter h = new ContentDisposition( 0, "attachment; filename=genome.jpeg;\n modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"); QCOMPARE( h->disposition(), CDattachment ); QCOMPARE( h->filename(), QString( "genome.jpeg" ) ); delete h; // TODO: test for case-insensitive disposition value } void HeaderTest::testContentTypeHeader() { ContentType* h; // empty header h = new ContentType(); QVERIFY( h->isEmpty() ); // Empty content-type means text/plain (RFC 2045 §5.2) QVERIFY( h->isPlainText() ); QVERIFY( h->isText() ); // set a mimetype h->setMimeType( "text/plain" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->mimeType(), QByteArray( "text/plain" ) ); QCOMPARE( h->mediaType(), QByteArray("text") ); QCOMPARE( h->subType(), QByteArray("plain") ); QVERIFY( h->isText() ); QVERIFY( h->isPlainText() ); QVERIFY( !h->isMultipart() ); QVERIFY( !h->isPartial() ); QVERIFY( h->isMediatype( "text" ) ); QVERIFY( h->isSubtype( "plain" ) ); QCOMPARE( h->as7BitString( true ), QByteArray( "Content-Type: text/plain" ) ); // add some parameters h->setId( "bla" ); h->setCharset( "us-ascii" ); QCOMPARE( h->as7BitString( false ), QByteArray( "text/plain; charset=\"us-ascii\"; id=\"bla\"" ) ); // clear header h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a complete header h = new ContentType( 0, "text/plain; charset=us-ascii (Plain text)" ); QVERIFY( h->isPlainText() ); QCOMPARE( h->charset(), QByteArray( "us-ascii" ) ); delete h; // bug #136631 (name with rfc 2231 style parameter wrapping) h = new ContentType( 0, "text/plain;\n name*0=\"PIN_Brief_box1@xx.xxx.censored_Konfigkarte.confi\";\n name*1=\"guration.txt\"" ); QVERIFY( h->isPlainText() ); QCOMPARE( h->name(), QString( "PIN_Brief_box1@xx.xxx.censored_Konfigkarte.configuration.txt" ) ); delete h; } void HeaderTest::testTokenHeader() { Token *h; // empty header h = new Token(); QVERIFY( h->isEmpty() ); // set a token h->setToken( "bla" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( false ), QByteArray( "bla" ) ); // clear it again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a header h = new Token( 0, "value (comment)" ); QCOMPARE( h->token(), QByteArray("value") ); QCOMPARE( h->as7BitString( false ), QByteArray("value") ); delete h; } void HeaderTest::testContentTransferEncoding() { ContentTransferEncoding *h; // empty header h = new ContentTransferEncoding(); QVERIFY( h->isEmpty() ); // set an encoding h->setEncoding( CEbinary ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( true ), QByteArray("Content-Transfer-Encoding: binary") ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a header h = new ContentTransferEncoding( 0, "(comment) base64" ); QCOMPARE( h->encoding(), CEbase64 ); QCOMPARE( h->as7BitString( false ), QByteArray("base64") ); delete h; } void HeaderTest::testPhraseListHeader() { PhraseList *h; // empty header h = new PhraseList(); QVERIFY( h->isEmpty() ); delete h; // parse a simple phrase list h = new PhraseList( 0, "foo,\n bar" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->phrases().count(), 2 ); QStringList phrases = h->phrases(); QCOMPARE( phrases.takeFirst(), QString( "foo" ) ); QCOMPARE( phrases.takeFirst(), QString( "bar" ) ); QCOMPARE( h->as7BitString( false ), QByteArray("foo, bar") ); // clear header h->clear(); QVERIFY( h->isEmpty() ); delete h; // TODO: encoded/quoted phrases } void HeaderTest::testDotAtomHeader() { DotAtom *h; // empty header h = new DotAtom; QVERIFY( h->isEmpty() ); // parse a simple dot atom h->from7BitString( "1.0 (mime version)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->asUnicodeString(), QString( "1.0" ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // TODO: more complex atoms } void HeaderTest::testDateHeader() { Date *h; // empty header h = new Date(); QVERIFY( h->isEmpty() ); // parse a simple date h->from7BitString( "Fri, 21 Nov 1997 09:55:06 -0600" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1997, 11, 21 ) ); QCOMPARE( h->dateTime().time(), QTime( 9, 55, 6 ) ); QCOMPARE( h->dateTime().utcOffset(), -6 * 3600 ); QCOMPARE( h->as7BitString(), QByteArray( "Date: Fri, 21 Nov 1997 09:55:06 -0600" ) ); // clear it again h->clear(); QVERIFY( h->isEmpty() ); delete h; // white spaces and comment (from RFC 2822, Appendix A.5) h = new Date( 0, "Thu,\n 13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1969, 2, 13 ) ); QCOMPARE( h->dateTime().time(), QTime( 23, 32 ) ); QCOMPARE( h->dateTime().utcOffset(), -12600 ); QCOMPARE( h->as7BitString( false ), QByteArray( "Thu, 13 Feb 1969 23:32 -0330" ) ); delete h; // obsolete date format (from RFC 2822, Appendix A.6.2) h = new Date( 0, "21 Nov 97 09:55:06 GMT" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1997, 11, 21 ) ); QCOMPARE( h->dateTime().time(), QTime( 9, 55, 6 ) ); QCOMPARE( h->dateTime().utcOffset(), 0 ); delete h; // obsolete whitespaces and commnets (from RFC 2822, Appendix A.6.3) h = new Date( 0, "Fri, 21 Nov 1997 09(comment): 55 : 06 -0600" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1997, 11, 21 ) ); QCOMPARE( h->dateTime().time(), QTime( 9, 55, 6 ) ); QCOMPARE( h->dateTime().utcOffset(), -6 * 3600 ); delete h; + + // Make sure uppercase OCT is parsed correctly - bug 150620 + h = new Date( 0, "08 OCT 08 16:54:05 +0000" ); + QVERIFY( !h->isEmpty() ); + QCOMPARE( h->dateTime().date(), QDate( 2008, 10, 8 ) ); + QCOMPARE( h->dateTime().time(), QTime( 16, 54, 05 ) ); + QCOMPARE( h->dateTime().utcOffset(), 0 ); + delete h; } void HeaderTest::testLinesHeader() { Lines *h; // empty header h = new Lines(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); // set some content h->setNumberOfLines( 5 ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString(), QByteArray( "Lines: 5" ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse header with comment h = new Lines( 0, "(this is a comment) 10 (and yet another comment)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->numberOfLines(), 10 ); delete h; } void HeaderTest::testNewsgroupsHeader() { Newsgroups *h; // empty header h = new Newsgroups(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); // set newsgroups QList groups; groups << "gmane.comp.kde.devel.core" << "gmane.comp.kde.devel.buildsystem"; h->setGroups( groups ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString(), QByteArray( "Newsgroups: gmane.comp.kde.devel.core,gmane.comp.kde.devel.buildsystem" ) ); // and clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a header h = new Newsgroups( 0, "gmane.comp.kde.devel.core,gmane.comp.kde.devel.buildsystem" ); groups = h->groups(); QCOMPARE( groups.count(), 2 ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.core") ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.buildsystem") ); delete h; // same again, this time with whitespaces and comments h = new Newsgroups(); h->from7BitString( "(comment) gmane.comp.kde.devel.core (second comment),\n gmane.comp.kde.devel.buildsystem (that all)" ); groups = h->groups(); QCOMPARE( groups.count(), 2 ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.core") ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.buildsystem") ); delete h; } void HeaderTest::testControlHeader() { Control *h; // empty header h = new Control(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); // set some content h->setCancel( "" ); QVERIFY( !h->isEmpty() ); QVERIFY( h->isCancel() ); QCOMPARE( h->as7BitString(), QByteArray( "Control: cancel " ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a control header h = new Control( 0, "cancel " ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->parameter(), QByteArray("") ); QVERIFY( h->isCancel() ); QCOMPARE( h->controlType(), QByteArray("cancel") ); delete h; } void HeaderTest::testReturnPath() { ReturnPath *h; h = new ReturnPath(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); h->from7BitString( "" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( true ), QByteArray( "Return-Path: " ) ); delete h; } void HeaderTest::noAbstractHeaders() { From* h2 = new From(); delete h2; Sender* h3 = new Sender(); delete h3; To* h4 = new To(); delete h4; Cc* h5 = new Cc(); delete h5; Bcc* h6 = new Bcc(); delete h6; ReplyTo* h7 = new ReplyTo(); delete h7; Keywords* h8 = new Keywords(); delete h8; MIMEVersion* h9 = new MIMEVersion(); delete h9; MessageID* h10 = new MessageID(); delete h10; ContentID* h11 = new ContentID(); delete h11; Supersedes* h12 = new Supersedes(); delete h12; InReplyTo* h13 = new InReplyTo(); delete h13; References* h14 = new References(); delete h14; Generic* h15 = new Generic(); delete h15; Subject* h16 = new Subject(); delete h16; Organization* h17 = new Organization(); delete h17; ContentDescription* h18 = new ContentDescription(); delete h18; FollowUpTo* h22 = new FollowUpTo(); delete h22; UserAgent* h24 = new UserAgent(); delete h24; } void HeaderTest::testInvalidButOkQEncoding() { // A stray '?' should not confuse the parser Subject subject; subject.from7BitString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC?" "?=" ); QCOMPARE( subject.as7BitString( false ), QByteArray( "Why? Why do some clients violate the RFC?" ) ); } void HeaderTest::testInvalidQEncoding_data() { QTest::addColumn("encodedWord"); // All examples below should not be treated as invalid encoded strings, since the '?=' is missing QTest::newRow("") << QString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC??" ); QTest::newRow("") << QString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC?" ); QTest::newRow("") << QString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC" ); } void HeaderTest::testInvalidQEncoding() { using namespace HeaderParsing; QFETCH( QString,encodedWord ); const char *data = encodedWord.toAscii().data(); const char *start = data + 1; const char *end = data + strlen( data ); QString result; QByteArray language; QByteArray usedCS; QVERIFY( !parseEncodedWord( start, end, result, language, usedCS ) ); } #include "headertest.moc"