diff --git a/akonadi/CMakeLists.txt b/akonadi/CMakeLists.txt index bed922a13..f9c48e59e 100644 --- a/akonadi/CMakeLists.txt +++ b/akonadi/CMakeLists.txt @@ -1,268 +1,270 @@ project(akonadi-kde) add_definitions( -DKDE_DEFAULT_DEBUG_AREA=5250 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) if(CMAKE_COMPILE_GCOV) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") endif(CMAKE_COMPILE_GCOV) if (KDE4_BUILD_TESTS) # only with this macro the AKONADI_TESTS_EXPORT macro will do something add_definitions(-DCOMPILING_TESTS) add_subdirectory( tests ) endif (KDE4_BUILD_TESTS) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_subdirectory( kabc ) add_subdirectory( kmime ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${QT_QTDBUS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${KDE4_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR}/akonadi/private ) # libakonadi-kde set( akonadikde_LIB_SRC entity.cpp # keep it at top to not break enable-final agentbase.cpp agentfilterproxymodel.cpp agentinstance.cpp agentinstancecreatejob.cpp agentinstancemodel.cpp agentinstancewidget.cpp agentmanager.cpp agenttype.cpp agenttypemodel.cpp agenttypewidget.cpp agenttypedialog.cpp attribute.cpp attributefactory.cpp cachepolicy.cpp cachepolicypage.cpp changerecorder.cpp collection.cpp collectioncopyjob.cpp collectioncreatejob.cpp collectiondeletejob.cpp collectiondialog.cpp collectionfilterproxymodel.cpp collectiongeneralpropertiespage.cpp collectionfetchjob.cpp collectionfetchscope.cpp collectionmodel.cpp collectionmodel_p.cpp collectionmodifyjob.cpp collectionmovejob.cpp collectionpathresolver.cpp collectionpropertiesdialog.cpp collectionpropertiespage.cpp collectionrequester.cpp collectionrightsattribute.cpp collectionselectjob.cpp collectionstatistics.cpp collectionstatisticsdelegate.cpp collectionstatisticsjob.cpp collectionstatisticsmodel.cpp collectionsync.cpp collectionview.cpp control.cpp descendantsproxymodel.cpp entitycache.cpp entitydisplayattribute.cpp entityhiddenattribute.cpp entitytreemodel.cpp entitytreemodel_p.cpp entityfilterproxymodel.cpp entitytreeviewstatesaver.cpp erroroverlay.cpp exception.cpp favoritecollectionsmodel.cpp firstrun.cpp flatcollectionproxymodel.cpp item.cpp itemcreatejob.cpp itemcopyjob.cpp itemdeletejob.cpp itemfetchjob.cpp itemfetchscope.cpp itemmodel.cpp itemmonitor.cpp itemmovejob.cpp + itemsearchjob.cpp itemserializer.cpp itemserializerplugin.cpp itemmodifyjob.cpp itemsync.cpp itemview.cpp job.cpp linkjob.cpp filteractionjob.cpp mimetypechecker.cpp monitor.cpp monitor_p.cpp pastehelper.cpp preprocessorbase.cpp protocolhelper.cpp resourcebase.cpp resourcescheduler.cpp resourceselectjob.cpp searchcreatejob.cpp selectionproxymodel.cpp selftestdialog.cpp session.cpp servermanager.cpp standardactionmanager.cpp statisticsproxymodel.cpp statisticstooltipproxymodel.cpp subscriptionjob.cpp subscriptionchangeproxymodel.cpp subscriptiondialog.cpp subscriptionmodel.cpp transactionjobs.cpp transactionsequence.cpp transportresourcebase.cpp unlinkjob.cpp # Temporary until ported to Qt-plugin framework pluginloader.cpp ) # DBus interfaces and adaptors set(akonadi_xml ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.NotificationManager.xml) set_source_files_properties(${akonadi_xml} PROPERTIES INCLUDE "notificationmessage_p.h") qt4_add_dbus_interface( akonadikde_LIB_SRC ${akonadi_xml} notificationmanagerinterface ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.AgentManager.xml ) qt4_add_dbus_interfaces( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Tracer.xml ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Resource.xml resourcebase.h Akonadi::ResourceBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Preprocessor.xml preprocessorbase.h Akonadi::PreprocessorBase ) qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Status.xml agentbase.h Akonadi::AgentBase ) # TODO move this to kdesupport/akonadi/interfaces qt4_add_dbus_adaptor( akonadikde_LIB_SRC interfaces/org.freedesktop.Akonadi.Resource.Transport.xml transportresourcebase_p.h Akonadi::TransportResourceBasePrivate ) # TODO merge with Akonadi.Control in kdesupport/akonadi/interfaces qt4_add_dbus_adaptor( akonadikde_LIB_SRC interfaces/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) #qt4_add_dbus_adaptor( akonadikde_LIB_SRC ${AKONADI_DBUS_INTERFACES_DIR}/org.freedesktop.Akonadi.Agent.Control.xml agentbase.h Akonadi::AgentBase ) kde4_add_ui_files( akonadikde_LIB_SRC cachepolicypage.ui collectiongeneralpropertiespage.ui subscriptiondialog.ui controlprogressindicator.ui selftestdialog.ui ) kde4_add_library( akonadi-kde SHARED ${akonadikde_LIB_SRC} ) macro_ensure_version( "4.2.0" ${KDE_VERSION} KDE_IS_AT_LEAST_42 ) target_link_libraries( akonadi-kde ${KDE4_SOLID_LIBS} ${QT_QTNETWORK_LIBRARY} ${QT_QTDBUS_LIBRARY} ${QT_QTSQL_LIBRARY} ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${AKONADI_COMMON_LIBRARIES} ) set( AKONADI_KDE_DEPS ${KDE4_KDEUI_LIBS} ${QT_QTDBUS_LIBRARY} ${QT_QTCORE_LIBRARY} ) if(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde LINK_INTERFACE_LIBRARIES ${AKONADI_KDE_DEPS}) else(${KDE_IS_AT_LEAST_42}) target_link_libraries( akonadi-kde ${AKONADI_KDE_DEPS}) endif(${KDE_IS_AT_LEAST_42}) set_target_properties( akonadi-kde PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install( TARGETS akonadi-kde EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install( FILES akonadi_export.h agentbase.h agentfilterproxymodel.h agentinstance.h agentinstancecreatejob.h agentinstancemodel.h agentinstancewidget.h agentmanager.h agenttype.h agenttypemodel.h agenttypewidget.h agenttypedialog.h attribute.h attributefactory.h cachepolicy.h changerecorder.h collection.h collectioncopyjob.h collectioncreatejob.h collectiondeletejob.h collectiondialog.h collectionfilterproxymodel.h collectionfetchjob.h collectionfetchscope.h collectionmodel.h collectionmodifyjob.h collectionpropertiesdialog.h collectionpropertiespage.h collectionrequester.h collectionstatisticsdelegate.h collectionstatisticsmodel.h collectionstatistics.h collectionstatisticsjob.h collectionview.h control.h descendantsproxymodel.h entity.h entitydisplayattribute.h entityhiddenattribute.h entitytreemodel.h entityfilterproxymodel.h entitytreeviewstatesaver.h exception.h favoritecollectionsmodel.h item.h itemcreatejob.h itemcopyjob.h itemdeletejob.h itemfetchjob.h itemfetchscope.h itemmodel.h itemmodifyjob.h itemmonitor.h itemmovejob.h itempayloadinternals_p.h + itemsearchjob.h itemserializerplugin.h itemsync.h itemview.h job.h linkjob.h filteractionjob.h mimetypechecker.h monitor.h qtest_akonadi.h preprocessorbase.h resourcebase.h searchcreatejob.h selectionproxymodel.h session.h servermanager.h standardactionmanager.h statisticsproxymodel.h statisticstooltipproxymodel.h transactionjobs.h transactionsequence.h transportresourcebase.h unlinkjob.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi COMPONENT Devel ) install( FILES collectionpathresolver_p.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/private COMPONENT Devel ) install( FILES kcfg2dbus.xsl DESTINATION ${DATA_INSTALL_DIR}/akonadi-kde ) diff --git a/akonadi/changerecorder.cpp b/akonadi/changerecorder.cpp index 84cb9f8ef..3c6b8af6f 100644 --- a/akonadi/changerecorder.cpp +++ b/akonadi/changerecorder.cpp @@ -1,194 +1,188 @@ /* Copyright (c) 2007 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 "changerecorder.h" #include "monitor_p.h" #include #include using namespace Akonadi; class Akonadi::ChangeRecorderPrivate : public MonitorPrivate { public: ChangeRecorderPrivate( ChangeRecorder* parent ) : MonitorPrivate( parent ), settings( 0 ), enableChangeRecording( true ) { } Q_DECLARE_PUBLIC( ChangeRecorder ) - NotificationMessage::List pendingNotifications; QSettings *settings; bool enableChangeRecording; - virtual void slotNotify( const NotificationMessage::List &msgs ) + virtual int pipelineSize() const { - if ( !enableChangeRecording ) { - foreach( const NotificationMessage &msg, msgs ) - processNotification( msg ); - return; - } + if ( enableChangeRecording ) + return 0; // we fill the pipeline ourselves when using change recording + return MonitorPrivate::pipelineSize(); + } + virtual void slotNotify( const NotificationMessage::List &msgs ) + { Q_Q( ChangeRecorder ); - int oldChanges = pendingNotifications.count(); - foreach ( const NotificationMessage &msg, msgs ) { - if ( acceptNotification( msg ) ) - NotificationMessage::appendAndCompress( pendingNotifications, msg ); - } - if ( pendingNotifications.count() != oldChanges ) { + const int oldChanges = pendingNotifications.size(); + MonitorPrivate::slotNotify( msgs ); // with change recording disabled this will automatically take care of dispatching notification messages + if ( enableChangeRecording && pendingNotifications.size() != oldChanges ) { saveNotifications(); emit q->changesAdded(); } } void loadNotifications() { pendingNotifications.clear(); QStringList list; settings->beginGroup( QLatin1String( "ChangeRecorder" ) ); int size = settings->beginReadArray( QLatin1String( "change" ) ); for ( int i = 0; i < size; ++i ) { settings->setArrayIndex( i ); NotificationMessage msg; msg.setSessionId( settings->value( QLatin1String( "sessionId" ) ).toByteArray() ); msg.setType( (NotificationMessage::Type)settings->value( QLatin1String( "type" ) ).toInt() ); msg.setOperation( (NotificationMessage::Operation)settings->value( QLatin1String( "op" ) ).toInt() ); msg.setUid( settings->value( QLatin1String( "uid" ) ).toLongLong() ); msg.setRemoteId( settings->value( QLatin1String( "rid" ) ).toString() ); msg.setResource( settings->value( QLatin1String( "resource" ) ).toByteArray() ); msg.setParentCollection( settings->value( QLatin1String( "parentCol" ) ).toLongLong() ); msg.setParentDestCollection( settings->value( QLatin1String( "parentDestCol" ) ).toLongLong() ); msg.setMimeType( settings->value( QLatin1String( "mimeType" ) ).toString() ); list = settings->value( QLatin1String( "itemParts" ) ).toStringList(); QSet itemParts; Q_FOREACH( const QString &entry, list ) itemParts.insert( entry.toLatin1() ); msg.setItemParts( itemParts ); pendingNotifications << msg; } settings->endArray(); settings->endGroup(); } void saveNotifications() { if ( !settings ) return; settings->beginGroup( QLatin1String( "ChangeRecorder" ) ); settings->beginWriteArray( QLatin1String( "change" ), pendingNotifications.count() ); for ( int i = 0; i < pendingNotifications.count(); ++i ) { settings->setArrayIndex( i ); NotificationMessage msg = pendingNotifications.at( i ); settings->setValue( QLatin1String( "sessionId" ), msg.sessionId() ); settings->setValue( QLatin1String( "type" ), msg.type() ); settings->setValue( QLatin1String( "op" ), msg.operation() ); settings->setValue( QLatin1String( "uid" ), msg.uid() ); settings->setValue( QLatin1String( "rid" ), msg.remoteId() ); settings->setValue( QLatin1String( "resource" ), msg.resource() ); settings->setValue( QLatin1String( "parentCol" ), msg.parentCollection() ); settings->setValue( QLatin1String( "parentDestCol" ), msg.parentDestCollection() ); settings->setValue( QLatin1String( "mimeType" ), msg.mimeType() ); QStringList list; const QSet itemParts = msg.itemParts(); QSetIterator it( itemParts ); while ( it.hasNext() ) list.append( QString::fromLatin1( it.next() ) ); settings->setValue( QLatin1String( "itemParts" ), list ); } settings->endArray(); settings->endGroup(); } }; ChangeRecorder::ChangeRecorder(QObject * parent) : Monitor( new ChangeRecorderPrivate( this ), parent ) { Q_D( ChangeRecorder ); d->init(); d->connectToNotificationManager(); } ChangeRecorder::~ ChangeRecorder() { Q_D( ChangeRecorder ); d->saveNotifications(); } void ChangeRecorder::setConfig(QSettings * settings) { Q_D( ChangeRecorder ); if ( settings ) { d->settings = settings; Q_ASSERT( d->pendingNotifications.isEmpty() ); d->loadNotifications(); } else if ( d->settings ) { d->saveNotifications(); d->settings = settings; } } void ChangeRecorder::replayNext() { - bool nothing = true; Q_D( ChangeRecorder ); - while( !d->pendingNotifications.isEmpty() ) { + if ( !d->pendingNotifications.isEmpty() ) { const NotificationMessage msg = d->pendingNotifications.first(); - if ( d->processNotification( msg ) ) { - nothing = false; - break; - } - d->pendingNotifications.takeFirst(); - } - if( nothing ) { + if ( d->ensureDataAvailable( msg ) ) + d->emitNotification( msg ); + else + d->pipeline.enqueue( msg ); + } else { // This is necessary when none of the notifications were accepted / processed // above, and so there is no one to call changeProcessed() and the ChangeReplay task // will be stuck forever in the ResourceScheduler. emit nothingToReplay(); } d->saveNotifications(); } bool ChangeRecorder::isEmpty() const { Q_D( const ChangeRecorder ); return d->pendingNotifications.isEmpty(); } void ChangeRecorder::changeProcessed() { Q_D( ChangeRecorder ); if ( !d->pendingNotifications.isEmpty() ) d->pendingNotifications.removeFirst(); d->saveNotifications(); } void ChangeRecorder::setChangeRecordingEnabled( bool enable ) { Q_D( ChangeRecorder ); d->enableChangeRecording = enable; Q_ASSERT( enable || d->pendingNotifications.isEmpty() ); } #include "changerecorder.moc" diff --git a/akonadi/collectionrequester.cpp b/akonadi/collectionrequester.cpp index afff840e5..7e126f832 100644 --- a/akonadi/collectionrequester.cpp +++ b/akonadi/collectionrequester.cpp @@ -1,151 +1,151 @@ /* Copyright 2008 Ingo Klöcker 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 "collectionrequester.h" #include "collectiondialog.h" #include #include #include #include #include #include #include using namespace Akonadi; class CollectionRequester::Private { public: Private( CollectionRequester *parent ) : q( parent ), edit( 0 ), button( 0 ), collectionDialog( 0 ) { } ~Private() { } void init(); // slots void _k_slotOpenDialog(); CollectionRequester *q; Collection collection; KLineEdit *edit; KPushButton *button; CollectionDialog *collectionDialog; }; void CollectionRequester::Private::init() { q->setMargin( 0 ); edit = new KLineEdit( q ); edit->setReadOnly( true ); edit->setClearButtonShown( false ); button = new KPushButton( q ); button->setIcon( KIcon( QLatin1String( "document-open" ) ) ); const int buttonSize = edit->sizeHint().height(); button->setFixedSize( buttonSize, buttonSize ); button->setToolTip( i18n( "Open collection dialog" ) ); - q->setSpacing( KDialog::spacingHint() ); + q->setSpacing( -1 ); edit->installEventFilter( q ); q->setFocusProxy( edit ); q->setFocusPolicy( Qt::StrongFocus ); q->connect( button, SIGNAL( clicked() ), q, SLOT( _k_slotOpenDialog() ) ); QAction *openAction = new QAction( q ); openAction->setShortcut( KStandardShortcut::Open ); q->connect( openAction, SIGNAL( triggered( bool ) ), q, SLOT( _k_slotOpenDialog() ) ); collectionDialog = new CollectionDialog( q ); collectionDialog->setSelectionMode( QAbstractItemView::SingleSelection ); } void CollectionRequester::Private::_k_slotOpenDialog() { CollectionDialog *dlg = collectionDialog; if ( dlg->exec() != QDialog::Accepted ) return; q->setCollection( dlg->selectedCollection() ); } CollectionRequester::CollectionRequester( QWidget *parent ) : KHBox( parent ), d( new Private( this ) ) { d->init(); } CollectionRequester::CollectionRequester( const Akonadi::Collection &collection, QWidget *parent ) : KHBox( parent ), d( new Private( this ) ) { d->init(); setCollection( collection ); } CollectionRequester::~CollectionRequester() { delete d; } Collection CollectionRequester::collection() const { return d->collection; } void CollectionRequester::setCollection( const Collection& collection ) { d->collection = collection; d->edit->setText( collection.isValid() ? collection.name() : i18n( "no collection" ) ); } void CollectionRequester::setMimeTypeFilter( const QStringList &mimeTypes ) { if ( d->collectionDialog ) d->collectionDialog->setMimeTypeFilter( mimeTypes ); } QStringList CollectionRequester::mimeTypeFilter() const { if ( d->collectionDialog ) return d->collectionDialog->mimeTypeFilter(); else return QStringList(); } #include "collectionrequester.moc" diff --git a/akonadi/itemsearchjob.cpp b/akonadi/itemsearchjob.cpp new file mode 100644 index 000000000..d77ec0369 --- /dev/null +++ b/akonadi/itemsearchjob.cpp @@ -0,0 +1,103 @@ +/* + Copyright (c) 2009 Tobias Koenig + + 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 "itemsearchjob.h" + +#include "imapparser_p.h" +#include "job_p.h" + +using namespace Akonadi; + +class Akonadi::ItemSearchJobPrivate : public JobPrivate +{ + public: + ItemSearchJobPrivate( ItemSearchJob *parent, const QString &query ) + : JobPrivate( parent ), mQuery( query ) + { + } + + QString mQuery; + Item::List mItems; +}; + +ItemSearchJob::ItemSearchJob( const QString & query, QObject * parent ) + : Job( new ItemSearchJobPrivate( this, query ), parent ) +{ +} + +ItemSearchJob::~ItemSearchJob() +{ +} + +void ItemSearchJob::doStart() +{ + Q_D( ItemSearchJob ); + + QByteArray command = d->newTag() + " SEARCH "; + command += ImapParser::quote( d->mQuery.toUtf8() ); + command += '\n'; + d->writeData( command ); +} + +void ItemSearchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data ) +{ + Q_D( ItemSearchJob ); + + if ( tag == "*" ) { + int begin = data.indexOf( "SEARCH" ); + if ( begin >= 0 ) { + + // split search response into key/value pairs + QList searchResponse; + ImapParser::parseParenthesizedList( data, searchResponse, begin + 7 ); + + // create a new item object + Item::Id uid = -1; + + for ( int i = 0; i < searchResponse.count() - 1; i += 2 ) { + const QByteArray key = searchResponse.value( i ); + const QByteArray value = searchResponse.value( i + 1 ); + + if ( key == "UID" ) + uid = value.toLongLong(); + } + + if ( uid < 0 ) { + kWarning() << "Broken search response: UID missing!"; + return; + } + + Item item( uid ); + if ( !item.isValid() ) + return; + + d->mItems.append( item ); + return; + } + } +} + +Item::List ItemSearchJob::items() const +{ + Q_D( const ItemSearchJob ); + + return d->mItems; +} + +#include "itemsearchjob.moc" diff --git a/akonadi/itemsearchjob.h b/akonadi/itemsearchjob.h new file mode 100644 index 000000000..aaba51f20 --- /dev/null +++ b/akonadi/itemsearchjob.h @@ -0,0 +1,93 @@ +/* + Copyright (c) 2009 Tobias Koenig + + 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 AKONADI_ITEMSEARCHJOB_H +#define AKONADI_ITEMSEARCHJOB_H + +#include +#include + +namespace Akonadi { + +class ItemSearchJobPrivate; + +/** + * @short Job that searches for items in the Akonadi storage. + * + * This job searches for items that match a given search query and returns + * the list of item ids. + * + * @code + * + * const QString query = "..."; // some sparql query + * + * Akonadi::ItemSearchJob *job = new Akonadi::ItemSearchJob( query ); + * connect( job, SIGNAL( result( KJob* ) ), this, SLOT( searchResult( KJob* ) ) ); + * + * ... + * + * MyClass::searchResult( KJob *job ) + * { + * Akonadi::ItemSearchJob *searchJob = qobject_cast( job ); + * const Akonadi::Item::List items = searchJob->items(); + * foreach ( const Akonadi::Item &item, items ) { + * // fetch the full payload of 'item' + * } + * } + * + * @endcode + * + * @author Tobias Koenig + */ +class AKONADI_EXPORT ItemSearchJob : public Job +{ + Q_OBJECT + + public: + /** + * Creates an item search job. + * + * @param query The search query in SPARQL format. + * @param parent The parent object. + */ + ItemSearchJob( const QString &query, QObject *parent = 0 ); + + /** + * Destroys the item search job. + */ + ~ItemSearchJob(); + + /** + * Returns the items that matched the search query. + * + * @note The items only contain the uid but no payload. + */ + Item::List items() const; + + protected: + void doStart(); + virtual void doHandleResponse( const QByteArray &tag, const QByteArray &data ); + + private: + Q_DECLARE_PRIVATE( ItemSearchJob ) +}; + +} + +#endif diff --git a/akonadi/monitor.h b/akonadi/monitor.h index 0bdbc0b76..7ef1e0256 100644 --- a/akonadi/monitor.h +++ b/akonadi/monitor.h @@ -1,379 +1,377 @@ /* Copyright (c) 2006 - 2007 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. */ #ifndef AKONADI_MONITOR_H #define AKONADI_MONITOR_H #include #include #include namespace Akonadi { class CollectionFetchScope; class CollectionStatistics; class Item; class ItemFetchScope; class MonitorPrivate; class Session; /** * @short Monitors an item or collection for changes. * * The Monitor emits signals if some of these objects are changed or * removed or new ones are added to the Akonadi storage. * * Optionally, the changed objects can be fetched automatically from the server. * To enable this, see itemFetchScope() and collectionFetchScope(). * * @todo: distinguish between monitoring collection properties and collection content. * @todo: special case for collection content counts changed * * @author Volker Krause */ class AKONADI_EXPORT Monitor : public QObject { Q_OBJECT public: /** * Creates a new monitor. * * @param parent The parent object. */ explicit Monitor( QObject *parent = 0 ); /** * Destroys the monitor. */ virtual ~Monitor(); /** * Sets whether the specified collection shall be monitored for changes. * * @param collection The collection to monitor. * If this collection is Collection::root(), all collections * in the Akonadi storage will be monitored. */ void setCollectionMonitored( const Collection &collection, bool monitored = true ); /** * Sets whether the specified item shall be monitored for changes. * * @param item The item to monitor. */ void setItemMonitored( const Item &item, bool monitored = true ); /** * Sets whether the specified resource shall be monitored for changes. * * @param resource The resource identifier. */ void setResourceMonitored( const QByteArray &resource, bool monitored = true ); /** * Sets whether objects of the specified mime type shall be monitored for changes. * * @param mimetype The mime type to monitor. */ void setMimeTypeMonitored( const QString &mimetype, bool monitored = true ); /** * Sets whether all items shall be monitored. */ void setAllMonitored( bool monitored = true ); /** * Ignores all change notifications caused by the given session. * * @param session The session you want to ignore. */ void ignoreSession( Session *session ); /** * Enables automatic fetching of changed collections from the Akonadi storage. * * @param enable @c true enables automatic fetching, @c false disables automatic fetching. */ void fetchCollection( bool enable ); /** * Enables automatic fetching of changed collection statistics information from * the Akonadi storage. * * @param enable @c true to enables automatic fetching, @c false disables automatic fetching. */ void fetchCollectionStatistics( bool enable ); /** * Sets the item fetch scope. * * Controls how much of an item's data is fetched from the server, e.g. * whether to fetch the full item payload or only meta data. * * @param fetchScope The new scope for item fetch operations. * * @see itemFetchScope() */ void setItemFetchScope( const ItemFetchScope &fetchScope ); /** * Returns the item fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the ItemFetchScope documentation * for an example. * * @return a reference to the current item fetch scope * * @see setItemFetchScope() for replacing the current item fetch scope */ ItemFetchScope &itemFetchScope(); /** * Sets the collection fetch scope. * * Controls which collections are monitored and how much of a collection's data * is fetched from the server. * * @param fetchScope The new scope for collection fetch operations. * * @see collectionFetchScope() * @since 4.4 */ void setCollectionFetchScope( const CollectionFetchScope &fetchScope ); /** * Returns the collection fetch scope. * * Since this returns a reference it can be used to conveniently modify the * current scope in-place, i.e. by calling a method on the returned reference * without storing it in a local variable. See the CollectionFetchScope documentation * for an example. * * @return a reference to the current collection fetch scope * * @see setCollectionFetchScope() for replacing the current collection fetch scope * @since 4.4 */ CollectionFetchScope &collectionFetchScope(); /** * Returns the list of collections being monitored. * * @since 4.3 */ Collection::List collectionsMonitored() const; /** * Returns the set of items being monitored. * * @since 4.3 */ QList itemsMonitored() const; /** * Returns the set of mimetypes being monitored. * * @since 4.3 */ QStringList mimeTypesMonitored() const; /** * Returns the set of identifiers for resources being monitored. * * @since 4.3 */ QList resourcesMonitored() const; /** * Returns true if everything is being monitored. * * @since 4.3 */ bool isAllMonitored() const; Q_SIGNALS: /** * This signal is emitted if a monitored item has changed, e.g. item parts have been modified. * * @param item The changed item. * @param partIdentifiers The identifiers of the item parts that has been changed. */ void itemChanged( const Akonadi::Item &item, const QSet &partIdentifiers ); /** * This signal is emitted if a monitored item has been moved between two collections * * @param item The moved item. * @param collectionSource The collection the item has been moved from. * @param collectionDestination The collection the item has been moved to. */ void itemMoved( const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination ); /** * This signal is emitted if an item has been added to a monitored collection in the Akonadi storage. * * @param item The new item. * @param collection The collection the item has been added to. */ void itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if * - a monitored item has been removed from the Akonadi storage * or * - a item has been removed from a monitored collection. * * @param item The removed item. */ void itemRemoved( const Akonadi::Item &item ); //TODO remove if we are sure nobody uses it any longer void itemRemoved( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if a reference to an item is added to a virtual collection. * @param item The linked item. * @param collection The collection the item is linked to. * * @since 4.2 */ void itemLinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if a reference to an item is removed from a virtual collection. * @param item The unlinked item. * @param collection The collection the item is unlinked from. * * @since 4.2 */ void itemUnlinked( const Akonadi::Item &item, const Akonadi::Collection &collection ); /** * This signal is emitted if a new collection has been added to a monitored collection in the Akonadi storage. * * @param collection The new collection. * @param parent The parent collection. */ void collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent ); /** * This signal is emitted if a monitored collection has been changed (properties or content). * * @param collection The changed collection. */ void collectionChanged( const Akonadi::Collection &collection ); /** * This signals is emitted if a monitored collection has been moved. * * @param collection The moved collection. * @param source The previous parent collection. * @param distination The new parent collection. */ void collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination ); /** * This signal is emitted if a monitored collection has been removed from the Akonadi storage. * * @param collection The removed collection. */ void collectionRemoved( const Akonadi::Collection &collection ); /** * This signal is emitted if the statistics information of a monitored collection * has changed. * * @param id The collection identifier of the changed collection. * @param statistics The updated collection statistics, invalid if automatic * fetching of statistics changes is disabled. */ void collectionStatisticsChanged( Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics ); /** * This signal is emitted if the Monitor starts or stops monitoring @p collection explicitly. * @param collection The collection * @param monitored Whether the collection is now being monitored or not. * * @since 4.3 */ void collectionMonitored( const Akonadi::Collection &collection, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring @p item explicitly. * @param item The item * @param monitored Whether the item is now being monitored or not. * * @since 4.3 */ void itemMonitored( const Akonadi::Item &item, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring the resource with the identifier @p identifier explicitly. * @param identifier The identifier of the resource. * @param monitored Whether the resource is now being monitored or not. * * @since 4.3 */ void resourceMonitored( const QByteArray &identifier, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring @p mimeType explicitly. * @param mimeType The mimeType. * @param monitored Whether the mimeType is now being monitored or not. * * @since 4.3 */ void mimeTypeMonitored( const QString &mimeType, bool monitored ); /** * This signal is emitted if the Monitor starts or stops monitoring everything. * @param monitored Whether everything is now being monitored or not. * * @since 4.3 */ void allMonitored( bool monitored ); protected: //@cond PRIVATE MonitorPrivate *d_ptr; explicit Monitor( MonitorPrivate *d, QObject *parent = 0 ); //@endcond private: Q_DECLARE_PRIVATE( Monitor ) //@cond PRIVATE Q_PRIVATE_SLOT( d_ptr, void slotSessionDestroyed( QObject* ) ) Q_PRIVATE_SLOT( d_ptr, void slotStatisticsChangedFinished( KJob* ) ) Q_PRIVATE_SLOT( d_ptr, void slotFlushRecentlyChangedCollections() ) Q_PRIVATE_SLOT( d_ptr, void slotNotify( const Akonadi::NotificationMessage::List& ) ) - Q_PRIVATE_SLOT( d_ptr, void slotItemJobFinished( KJob* ) ) - Q_PRIVATE_SLOT( d_ptr, void slotCollectionJobFinished( KJob* ) ) Q_PRIVATE_SLOT( d_ptr, void dataAvailable() ) //@endcond }; } #endif diff --git a/akonadi/monitor_p.cpp b/akonadi/monitor_p.cpp index 4f9606519..4ca3d8007 100644 --- a/akonadi/monitor_p.cpp +++ b/akonadi/monitor_p.cpp @@ -1,505 +1,319 @@ /* Copyright (c) 2007 Tobias Koenig 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. */ // @cond PRIVATE #include "monitor_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "itemfetchjob.h" #include "notificationmessage_p.h" #include "session.h" #include using namespace Akonadi; static const int PipelineSize = 5; MonitorPrivate::MonitorPrivate(Monitor * parent) : q_ptr( parent ), nm( 0 ), monitorAll( false ), collectionCache( 3*PipelineSize ), // needs to be at least 3x pipeline size for the collection move case itemCache( PipelineSize ), // needs to be at least 1x pipeline size fetchCollection( false ), fetchCollectionStatistics( false ) { } void MonitorPrivate::init() { QObject::connect( &collectionCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); QObject::connect( &itemCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable()) ); } bool MonitorPrivate::connectToNotificationManager() { NotificationMessage::registerDBusTypes(); if ( !nm ) nm = new org::freedesktop::Akonadi::NotificationManager( QLatin1String( "org.freedesktop.Akonadi" ), QLatin1String( "/notifications" ), QDBusConnection::sessionBus(), q_ptr ); else return true; if ( !nm ) { kWarning() << "Unable to connect to notification manager"; } else { QObject::connect( nm, SIGNAL(notify(Akonadi::NotificationMessage::List)), q_ptr, SLOT(slotNotify(Akonadi::NotificationMessage::List)) ); return true; } return false; } +int MonitorPrivate::pipelineSize() const +{ + return PipelineSize; +} + bool MonitorPrivate::acceptNotification(const NotificationMessage & msg) { if ( isSessionIgnored( msg.sessionId() ) ) return false; switch ( msg.type() ) { case NotificationMessage::InvalidType: kWarning() << "Received invalid change notification!"; return false; case NotificationMessage::Item: return isItemMonitored( msg.uid(), msg.parentCollection(), msg.parentDestCollection(), msg.mimeType(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); case NotificationMessage::Collection: return isCollectionMonitored( msg.uid(), msg.resource() ) || isCollectionMonitored( msg.parentCollection(), msg.resource() ) || isCollectionMonitored( msg.parentDestCollection(), msg.resource() ); } Q_ASSERT( false ); return false; } void MonitorPrivate::dispatchNotifications() { - while ( pipeline.size() < PipelineSize && !pendingNotifications.isEmpty() ) { + while ( pipeline.size() < pipelineSize() && !pendingNotifications.isEmpty() ) { const NotificationMessage msg = pendingNotifications.dequeue(); if ( ensureDataAvailable( msg ) && pipeline.isEmpty() ) emitNotification( msg ); else pipeline.enqueue( msg ); } } bool MonitorPrivate::ensureDataAvailable( const NotificationMessage &msg ) { bool allCached = true; if ( fetchCollection ) { if ( !collectionCache.ensureCached( msg.parentCollection(), mCollectionFetchScope ) ) allCached = false; if ( msg.operation() == NotificationMessage::Move && !collectionCache.ensureCached( msg.parentDestCollection(), mCollectionFetchScope ) ) allCached = false; } if ( msg.operation() == NotificationMessage::Remove ) return allCached; // the actual object is gone already, nothing to fetch there if ( msg.type() == NotificationMessage::Item && !mItemFetchScope.isEmpty() ) { if ( !itemCache.ensureCached( msg.uid(), mItemFetchScope ) ) allCached = false; } else if ( msg.type() == NotificationMessage::Collection && fetchCollection ) { if ( !collectionCache.ensureCached( msg.uid(), mCollectionFetchScope ) ) allCached = false; } return allCached; } void MonitorPrivate::emitNotification( const NotificationMessage &msg ) { const Collection parent = collectionCache.retrieve( msg.parentCollection() ); Collection destParent; if ( msg.operation() == NotificationMessage::Move ) destParent = collectionCache.retrieve( msg.parentDestCollection() ); if ( msg.type() == NotificationMessage::Collection ) { const Collection col = collectionCache.retrieve( msg.uid() ); emitCollectionNotification( msg, col, parent, destParent ); } else if ( msg.type() == NotificationMessage::Item ) { const Item item = itemCache.retrieve( msg.uid() ); emitItemNotification( msg, item, parent, destParent ); } } void MonitorPrivate::dataAvailable() { while ( !pipeline.isEmpty() ) { const NotificationMessage msg = pipeline.head(); if ( ensureDataAvailable( msg ) ) { emitNotification( msg ); pipeline.dequeue(); } else { break; } } dispatchNotifications(); } void MonitorPrivate::updatePendingStatistics( const NotificationMessage &msg ) { if ( msg.type() == NotificationMessage::Item ) { notifyCollectionStatisticsWatchers( msg.parentCollection(), msg.resource() ); } else if ( msg.type() == NotificationMessage::Collection && msg.operation() == NotificationMessage::Remove ) { // no need for statistics updates anymore recentlyChangedCollections.remove( msg.uid() ); } } -bool MonitorPrivate::processNotification(const NotificationMessage & msg) -{ - if ( !acceptNotification( msg ) ) - return false; - - if ( msg.type() == NotificationMessage::Item ) { - notifyCollectionStatisticsWatchers( msg.parentCollection(), msg.resource() ); - if ( !mItemFetchScope.isEmpty() && - ( msg.operation() == NotificationMessage::Add || msg.operation() == NotificationMessage::Move || - msg.operation() == NotificationMessage::Link ) ) { - Item item( msg.uid() ); - item.setRemoteId( msg.remoteId() ); - - ItemCollectionFetchJob *job = new ItemCollectionFetchJob( item, msg.parentCollection(), msg.parentDestCollection(), q_ptr ); - job->setFetchScope( mItemFetchScope ); - pendingJobs.insert( job, msg ); - QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotItemJobFinished(KJob*)) ); - return true; - } - if ( !mItemFetchScope.isEmpty() && msg.operation() == NotificationMessage::Modify ) { - Item item( msg.uid() ); - item.setRemoteId( msg.remoteId() ); - ItemFetchJob *job = new ItemFetchJob( item, q_ptr ); - job->setFetchScope( mItemFetchScope ); - pendingJobs.insert( job, msg ); - QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotItemJobFinished(KJob*)) ); - return true; - } - emitItemNotification( msg ); - return true; - - } else if ( msg.type() == NotificationMessage::Collection ) { - if ( msg.operation() != NotificationMessage::Remove && fetchCollection ) { - Collection::List list; - list << Collection( msg.uid() ); - if ( msg.operation() == NotificationMessage::Add || msg.operation() == NotificationMessage::Move ) - list << Collection( msg.parentCollection() ); - if ( msg.operation() == NotificationMessage::Move ) - list << Collection( msg.parentDestCollection() ); - CollectionFetchJob *job = new CollectionFetchJob( list, q_ptr ); - pendingJobs.insert( job, msg ); - QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotCollectionJobFinished(KJob*)) ); - return true; - } - if ( msg.operation() == NotificationMessage::Remove ) { - // no need for statistics updates anymore - recentlyChangedCollections.remove( msg.uid() ); - } - emitCollectionNotification( msg ); - return true; - - } else { - kWarning() << "Received unknown change notification!"; - } - return false; -} - void MonitorPrivate::slotSessionDestroyed( QObject * object ) { Session* session = qobject_cast( object ); if ( session ) sessions.removeAll( session->sessionId() ); } void MonitorPrivate::slotStatisticsChangedFinished( KJob* job ) { if ( job->error() ) { kWarning() << "Error on fetching collection statistics: " << job->errorText(); } else { CollectionStatisticsJob *statisticsJob = static_cast( job ); emit q_ptr->collectionStatisticsChanged( statisticsJob->collection().id(), statisticsJob->statistics() ); } } void MonitorPrivate::slotFlushRecentlyChangedCollections() { foreach( Collection::Id collection, recentlyChangedCollections ) { if ( fetchCollectionStatistics ) { fetchStatistics( collection ); } else { static const CollectionStatistics dummyStatistics; emit q_ptr->collectionStatisticsChanged( collection, dummyStatistics ); } } recentlyChangedCollections.clear(); } void MonitorPrivate::slotNotify( const NotificationMessage::List &msgs ) { foreach ( const NotificationMessage &msg, msgs ) { invalidateCaches( msg ); if ( acceptNotification( msg ) ) { updatePendingStatistics( msg ); NotificationMessage::appendAndCompress( pendingNotifications, msg ); } } dispatchNotifications(); } void MonitorPrivate::emitItemNotification( const NotificationMessage &msg, const Item &item, const Collection &collection, const Collection &collectionDest ) { Q_ASSERT( msg.type() == NotificationMessage::Item ); Collection col = collection; Collection colDest = collectionDest; if ( !col.isValid() ) { col = Collection( msg.parentCollection() ); col.setResource( QString::fromUtf8( msg.resource() ) ); } if ( !colDest.isValid() ) { colDest = Collection( msg.parentDestCollection() ); // FIXME setResource here required ? } Item it = item; if ( !it.isValid() ) { it = Item( msg.uid() ); it.setRemoteId( msg.remoteId() ); it.setMimeType( msg.mimeType() ); } switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->itemAdded( it, col ); break; case NotificationMessage::Modify: emit q_ptr->itemChanged( it, msg.itemParts() ); break; case NotificationMessage::Move: emit q_ptr->itemMoved( it, col, colDest ); break; case NotificationMessage::Remove: emit q_ptr->itemRemoved( it ); emit q_ptr->itemRemoved( it, col ); break; case NotificationMessage::Link: emit q_ptr->itemLinked( it, col ); break; case NotificationMessage::Unlink: emit q_ptr->itemUnlinked( it, col ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in item change notification"; break; } } void MonitorPrivate::emitCollectionNotification( const NotificationMessage &msg, const Collection &col, const Collection &par, const Collection &dest ) { Q_ASSERT( msg.type() == NotificationMessage::Collection ); Collection collection = col; if ( !collection.isValid() ) { collection = Collection( msg.uid() ); collection.setParentCollection( Collection( msg.parentCollection() ) ); collection.setResource( QString::fromUtf8( msg.resource() ) ); collection.setRemoteId( msg.remoteId() ); } Collection parent = par; if ( !parent.isValid() ) parent = Collection( msg.parentCollection() ); Collection destination = dest; if ( !destination.isValid() ) destination = Collection( msg.parentDestCollection() ); switch ( msg.operation() ) { case NotificationMessage::Add: emit q_ptr->collectionAdded( collection, parent ); break; case NotificationMessage::Modify: emit q_ptr->collectionChanged( collection ); break; case NotificationMessage::Move: emit q_ptr->collectionMoved( collection, parent, destination ); break; case NotificationMessage::Remove: emit q_ptr->collectionRemoved( collection ); break; default: kDebug() << "Unknown operation type" << msg.operation() << "in collection change notification"; } } -void MonitorPrivate::slotItemJobFinished( KJob* job ) -{ - if ( !pendingJobs.contains( job ) ) { - kWarning() << "Unknown job - wtf is going on here?"; - return; - } - NotificationMessage msg = pendingJobs.take( job ); - Item item; - Collection col; - Collection destCol; - if ( job->error() ) { - kWarning() << "Error on fetching item:" << job->errorText(); - } else { - ItemFetchJob *fetchJob = qobject_cast( job ); - if ( fetchJob && fetchJob->items().count() > 0 ) - item = fetchJob->items().first(); - ItemCollectionFetchJob *cfjob = qobject_cast( job ); - if ( cfjob ) { - item = cfjob->item(); - col = cfjob->collection(); - destCol = cfjob->destCollection(); - } - } - emitItemNotification( msg, item, col, destCol ); -} - -void MonitorPrivate::slotCollectionJobFinished( KJob* job ) -{ - if ( !pendingJobs.contains( job ) ) { - kWarning() << "Unknown job - wtf is going on here?"; - return; - } - NotificationMessage msg = pendingJobs.take( job ); - if ( job->error() ) { - kWarning() << "Error on fetching collection:" << job->errorText(); - } else { - CollectionFetchJob *listJob = qobject_cast( job ); - QHash cols; - foreach ( const Collection &c, listJob->collections() ) - cols.insert( c.id(), c ); - emitCollectionNotification( msg, cols.value( msg.uid() ), cols.value( msg.parentCollection() ), cols.value( msg.parentDestCollection() ) ); - } -} - void MonitorPrivate::invalidateCaches( const NotificationMessage &msg ) { // remove invalidates if ( msg.operation() == NotificationMessage::Remove ) { if ( msg.type() == NotificationMessage::Collection ) { collectionCache.invalidate( msg.uid() ); } else if ( msg.type() == NotificationMessage::Item ) { itemCache.invalidate( msg.uid() ); } } // modify removes the cache entry, as we need to re-fetch if ( msg.operation() == NotificationMessage::Modify ) { if ( msg.type() == NotificationMessage::Collection ) { collectionCache.update( msg.uid(), mCollectionFetchScope ); } else if ( msg.type() == NotificationMessage::Item ) { itemCache.update( msg.uid(), mItemFetchScope ); } } } - -ItemCollectionFetchJob::ItemCollectionFetchJob( const Item &item, Collection::Id collectionId, Collection::Id destCollectionId, QObject *parent ) - : Job( parent ), - mReferenceItem( item ), mCollectionId( collectionId ), mDestCollectionId( destCollectionId ) -{ -} - -ItemCollectionFetchJob::~ItemCollectionFetchJob() -{ -} - -Item ItemCollectionFetchJob::item() const -{ - return mItem; -} - -Collection ItemCollectionFetchJob::collection() const -{ - return mCollection; -} - -Collection ItemCollectionFetchJob::destCollection() const -{ - return mDestCollection; -} - -void ItemCollectionFetchJob::setFetchScope( const ItemFetchScope &fetchScope ) -{ - mFetchScope = fetchScope; -} - -void ItemCollectionFetchJob::doStart() -{ - CollectionFetchJob *listJob = new CollectionFetchJob( Collection( mCollectionId ), CollectionFetchJob::Base, this ); - connect( listJob, SIGNAL( result( KJob* ) ), SLOT( collectionJobDone( KJob* ) ) ); - addSubjob( listJob ); - - if ( mDestCollectionId > 0 ) { - CollectionFetchJob *destListJob = new CollectionFetchJob( Collection( mDestCollectionId ), CollectionFetchJob::Base, this ); - connect( destListJob, SIGNAL( result( KJob* ) ), SLOT( destCollectionJobDone( KJob* ) ) ); - addSubjob( destListJob ); - } - - - ItemFetchJob *fetchJob = new ItemFetchJob( mReferenceItem, this ); - fetchJob->setFetchScope( mFetchScope ); - connect( fetchJob, SIGNAL( result( KJob* ) ), SLOT( itemJobDone( KJob* ) ) ); - addSubjob( fetchJob ); -} - -void ItemCollectionFetchJob::collectionJobDone( KJob* job ) -{ - if ( !job->error() ) { - CollectionFetchJob *listJob = qobject_cast( job ); - if ( listJob->collections().isEmpty() ) { - setError( 1 ); - setErrorText( QLatin1String( "No collection found" ) ); - } else - mCollection = listJob->collections().first(); - } -} - -void ItemCollectionFetchJob::destCollectionJobDone( KJob* job ) -{ - if ( !job->error() ) { - CollectionFetchJob *listJob = qobject_cast( job ); - if ( listJob->collections().isEmpty() ) { - setError( 1 ); - setErrorText( QLatin1String( "No collection found" ) ); - } else - mDestCollection = listJob->collections().first(); - } -} - -void ItemCollectionFetchJob::itemJobDone( KJob* job ) -{ - if ( !job->error() ) { - ItemFetchJob *fetchJob = qobject_cast( job ); - if ( fetchJob->items().isEmpty() ) { - setError( 2 ); - setErrorText( QLatin1String( "No item found" ) ); - } else - mItem = fetchJob->items().first(); - - emitResult(); - } -} - // @endcond - -#include "monitor_p.moc" diff --git a/akonadi/monitor_p.h b/akonadi/monitor_p.h index 039dd361a..586d688fe 100644 --- a/akonadi/monitor_p.h +++ b/akonadi/monitor_p.h @@ -1,206 +1,164 @@ /* Copyright (c) 2007 Tobias Koenig 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 AKONADI_MONITOR_P_H #define AKONADI_MONITOR_P_H #include "monitor.h" #include "collection.h" #include "collectionstatisticsjob.h" #include "collectionfetchscope.h" #include "item.h" #include "itemfetchscope.h" #include "job.h" #include #include "notificationmanagerinterface.h" #include "entitycache_p.h" #include #include #include namespace Akonadi { class Monitor; /** * @internal */ class MonitorPrivate { public: MonitorPrivate( Monitor *parent ); virtual ~MonitorPrivate() {} void init(); Monitor *q_ptr; Q_DECLARE_PUBLIC( Monitor ) org::freedesktop::Akonadi::NotificationManager *nm; Collection::List collections; QSet resources; QSet items; QSet mimetypes; bool monitorAll; QList sessions; ItemFetchScope mItemFetchScope; CollectionFetchScope mCollectionFetchScope; - QHash pendingJobs; CollectionCache collectionCache; ItemCache itemCache; QQueue pendingNotifications; QQueue pipeline; bool fetchCollection; bool fetchCollectionStatistics; bool isCollectionMonitored( Collection::Id collection, const QByteArray &resource ) const { if ( monitorAll || isCollectionMonitored( collection ) || resources.contains( resource ) ) return true; return false; } bool isItemMonitored( Item::Id item, Collection::Id collection, Collection::Id collectionDest, const QString &mimetype, const QByteArray &resource ) const { if ( monitorAll || isCollectionMonitored( collection ) || isCollectionMonitored( collectionDest ) ||items.contains( item ) || resources.contains( resource ) || isMimeTypeMonitored( mimetype ) ) return true; return false; } bool isSessionIgnored( const QByteArray &sessionId ) const { return sessions.contains( sessionId ); } bool connectToNotificationManager(); bool acceptNotification( const NotificationMessage &msg ); void dispatchNotifications(); bool ensureDataAvailable( const NotificationMessage &msg ); void emitNotification( const NotificationMessage &msg ); void updatePendingStatistics( const NotificationMessage &msg ); - bool processNotification( const NotificationMessage &msg ); - void invalidateCaches( const NotificationMessage &msg ); + virtual int pipelineSize() const; + // private slots void dataAvailable(); void slotSessionDestroyed( QObject* ); void slotStatisticsChangedFinished( KJob* ); void slotFlushRecentlyChangedCollections(); virtual void slotNotify( const NotificationMessage::List &msgs ); - void slotItemJobFinished( KJob* job ); - void slotCollectionJobFinished( KJob *job ); void emitItemNotification( const NotificationMessage &msg, const Item &item = Item(), const Collection &collection = Collection(), const Collection &collectionDest = Collection() ); void emitCollectionNotification( const NotificationMessage &msg, const Collection &col = Collection(), const Collection &par = Collection(), const Collection &dest = Collection() ); private: // collections that need a statistics update QSet recentlyChangedCollections; bool isCollectionMonitored( Collection::Id collection ) const { if ( collections.contains( Collection( collection ) ) ) return true; if ( collections.contains( Collection::root() ) ) return true; return false; } bool isMimeTypeMonitored( const QString& mimetype ) const { if ( mimetypes.contains( mimetype ) ) return true; KMimeType::Ptr mimeType = KMimeType::mimeType( mimetype, KMimeType::ResolveAliases ); if ( mimeType.isNull() ) return false; foreach ( const QString &mt, mimetypes ) { if ( mimeType->is( mt ) ) return true; } return false; } void fetchStatistics( Collection::Id colId ) { CollectionStatisticsJob *job = new CollectionStatisticsJob( Collection( colId ), q_ptr ); QObject::connect( job, SIGNAL(result(KJob*)), q_ptr, SLOT(slotStatisticsChangedFinished(KJob*)) ); } void notifyCollectionStatisticsWatchers( Collection::Id collection, const QByteArray &resource ) { if ( isCollectionMonitored( collection, resource ) ) { if (recentlyChangedCollections.empty() ) QTimer::singleShot( 500, q_ptr, SLOT(slotFlushRecentlyChangedCollections()) ); recentlyChangedCollections.insert( collection ); } } }; - -/** - * @internal - * - * A job which fetches an item and a collection. - */ -class AKONADI_EXPORT ItemCollectionFetchJob : public Job -{ - Q_OBJECT - - public: - explicit ItemCollectionFetchJob( const Item &item, Collection::Id collectionId, Collection::Id destCollectionId, QObject *parent = 0 ); - ~ItemCollectionFetchJob(); - - Item item() const; - Collection collection() const; - Collection destCollection() const; - - void setFetchScope( const ItemFetchScope &fetchScope ); - - protected: - virtual void doStart(); - - private Q_SLOTS: - void collectionJobDone( KJob* job ); - void destCollectionJobDone( KJob* job ); - void itemJobDone( KJob* job ); - - private: - Item mReferenceItem; - Collection::Id mCollectionId; - Collection::Id mDestCollectionId; - - Item mItem; - Collection mCollection; - Collection mDestCollection; - ItemFetchScope mFetchScope; -}; - } #endif diff --git a/akonadi/tests/CMakeLists.txt b/akonadi/tests/CMakeLists.txt index 9de1ab49d..2d7576566 100644 --- a/akonadi/tests/CMakeLists.txt +++ b/akonadi/tests/CMakeLists.txt @@ -1,127 +1,129 @@ if(${EXECUTABLE_OUTPUT_PATH}) set( PREVIOUS_EXEC_OUTPUT_PATH ${EXECUTABLE_OUTPUT_PATH} ) else(${EXECUTABLE_OUTPUT_PATH}) set( PREVIOUS_EXEC_OUTPUT_PATH . ) endif(${EXECUTABLE_OUTPUT_PATH}) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) include_directories( ${CMAKE_SOURCE_DIR}/akonadi ${CMAKE_CURRENT_SOURCE_DIR}/../ ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/../ ${Boost_INCLUDE_DIR} ${AKONADI_INCLUDE_DIR} + ${AKONADI_INCLUDE_DIR}/akonadi/private ) # add testrunner (application for managing a self-contained test # environment) add_subdirectory(testrunner) # add benchmarker add_subdirectory(benchmarker) # convenience macro to add akonadi demo application macro(add_akonadi_demo _source) set(_test ${_source}) get_filename_component(_name ${_source} NAME_WE) kde4_add_executable(${_name} TEST ${_test}) target_link_libraries(${_name} akonadi-kde akonadi-kmime ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBS}) endmacro(add_akonadi_demo) # convenience macro to add akonadi qtestlib unit-tests macro(add_akonadi_test _source) set(_test ${_source}) get_filename_component(_name ${_source} NAME_WE) - kde4_add_unit_test(${_name} TESTNAME akonadi-${_name} ${_test}) - target_link_libraries(${_name} akonadi-kde akonadi-kmime ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${AKONADI_COMMON_LIBRARIES}) + kde4_add_unit_test(${_name} TESTNAME akonadi-${_name} ${_test} fakesession.cpp fakemonitor.cpp fakeserver.cpp) + target_link_libraries(${_name} akonadi-kde akonadi-kmime ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${AKONADI_COMMON_LIBRARIES}) endmacro(add_akonadi_test) # convenience macro to add akonadi testrunner unit-tests macro(add_akonadi_isolated_test _source) set(_test ${_source}) get_filename_component(_name ${_source} NAME_WE) kde4_add_executable(${_name} TEST ${_test}) target_link_libraries(${_name} akonadi-kde akonadi-kmime ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${AKONADI_COMMON_LIBRARIES}) # based on kde4_add_unit_test if (WIN32) get_target_property( _loc ${_name} LOCATION ) set(_executable ${_loc}.bat) set(_testrunner ${PREVIOUS_EXEC_OUTPUT_PATH}/akonaditest.bat) else (WIN32) set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_name}) set(_testrunner ${PREVIOUS_EXEC_OUTPUT_PATH}/akonaditest) endif (WIN32) if (UNIX) set(_executable ${_executable}.shell) set(_testrunner ${_testrunner}.shell) endif (UNIX) add_test( akonadi-mysql-db-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-mysql-db.xml ${_executable} ) add_test( akonadi-mysql-fs-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-mysql-fs.xml ${_executable} ) #add_test( akonadi-postgresql-fs-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-postgresql-fs.xml ${_executable} ) #add_test( akonadi-postgresql-fs-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-postgresql-fs.xml ${_executable} ) #add_test( akonadi-sqlite-${_name} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config-sqlite.xml ${_executable} ) endmacro(add_akonadi_isolated_test) # demo applications add_akonadi_demo(itemdumper.cpp) add_akonadi_demo(subscriber.cpp) add_akonadi_demo(headfetcher.cpp) add_akonadi_demo(agentinstancewidgettest.cpp) add_akonadi_demo(agenttypewidgettest.cpp) add_akonadi_demo(pluginloadertest.cpp) add_akonadi_demo(selftester.cpp) kde4_add_executable( akonadi-firstrun TEST ../firstrun.cpp firstrunner.cpp ) target_link_libraries( akonadi-firstrun akonadi-kde ${KDE4_KDEUI_LIBS} ) # qtestlib unit tests add_akonadi_test(imapparsertest.cpp) add_akonadi_test(imapsettest.cpp) add_akonadi_test(itemhydratest.cpp) add_akonadi_test(itemtest.cpp) add_akonadi_test(itemserializertest.cpp) add_akonadi_test(mimetypecheckertest.cpp) add_akonadi_test(protocolhelpertest.cpp) +add_akonadi_test(entitytreemodeltest.cpp) # qtestlib tests that need non-exported stuff from akonadi-kde kde4_add_unit_test(resourceschedulertest TESTNAME akonadi-resourceschedulertest resourceschedulertest.cpp ../resourcescheduler.cpp) target_link_libraries(resourceschedulertest akonadi-kde ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${AKONADI_COMMON_LIBRARIES}) # testrunner tests add_akonadi_isolated_test(testenvironmenttest.cpp) add_akonadi_isolated_test(autoincrementtest.cpp) add_akonadi_isolated_test(attributefactorytest.cpp) add_akonadi_isolated_test(collectionjobtest.cpp) add_akonadi_isolated_test(collectionpathresolvertest.cpp) add_akonadi_isolated_test(collectionattributetest.cpp) add_akonadi_isolated_test(itemfetchtest.cpp) add_akonadi_isolated_test(itemappendtest.cpp) add_akonadi_isolated_test(itemstoretest.cpp) add_akonadi_isolated_test(itemdeletetest.cpp) add_akonadi_isolated_test(entitycachetest.cpp) add_akonadi_isolated_test(monitortest.cpp) add_akonadi_isolated_test(searchjobtest.cpp) add_akonadi_isolated_test(changerecordertest.cpp) add_akonadi_isolated_test(resourcetest.cpp) add_akonadi_isolated_test(subscriptiontest.cpp) add_akonadi_isolated_test(transactiontest.cpp) add_akonadi_isolated_test(filteractiontest.cpp) add_akonadi_isolated_test(itemcopytest.cpp) add_akonadi_isolated_test(itemmovetest.cpp) add_akonadi_isolated_test(collectioncopytest.cpp) add_akonadi_isolated_test(collectionmovetest.cpp) add_akonadi_isolated_test(collectionsynctest.cpp) add_akonadi_isolated_test(itemsynctest.cpp) add_akonadi_isolated_test(linktest.cpp) add_akonadi_isolated_test(cachetest.cpp) add_akonadi_isolated_test(servermanagertest.cpp) add_akonadi_isolated_test(collectioncreator.cpp) add_akonadi_isolated_test(itembenchmark.cpp) diff --git a/akonadi/tests/entitytreemodeltest.cpp b/akonadi/tests/entitytreemodeltest.cpp new file mode 100644 index 000000000..c1be6034e --- /dev/null +++ b/akonadi/tests/entitytreemodeltest.cpp @@ -0,0 +1,81 @@ +/* + Copyright (c) 2009 Stephen Kelly + + 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 +#include "fakeserver.h" +#include "fakesession.h" +#include "fakemonitor.h" +// #include "modelspy.h" + +#include "entitytreemodel.h" + +using namespace Akonadi; + +class EntityTreeModelTest : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + + void init(); + + void testCollectionFetch(); + +private: + EntityTreeModel *m_model; +// ModelSpy *m_modelSpy; + FakeAkonadiServer *m_fakeServer; + FakeSession *m_fakeSession; + FakeMonitor *m_fakeMonitor; +}; + + +void EntityTreeModelTest::initTestCase() +{ + m_fakeServer = new FakeAkonadiServer(); + m_fakeServer->start(); + +} + +void EntityTreeModelTest::init() +{ + FakeSession *fakeSession = new FakeSession( "EntityTreeModelTest fake session", this); + FakeMonitor *fakeMonitor = new FakeMonitor(this); + m_model = new EntityTreeModel(fakeSession, fakeMonitor); +// m_modelSpy = new ModelSpy(this); +// m_modelSpy->setModel(m_model); + +} + +void EntityTreeModelTest::testCollectionFetch() +{ + kDebug() << 2; + QTest::qWait(1000); +// sleep(10); +// QList collectionChunks; +// m_fakeSession->firstListJobResult(collectionChunks); +} + + +// #endif +#include "entitytreemodeltest.moc" + +QTEST_KDEMAIN(EntityTreeModelTest, NoGUI) + diff --git a/akonadi/tests/fakemonitor.cpp b/akonadi/tests/fakemonitor.cpp new file mode 100644 index 000000000..61cc54d20 --- /dev/null +++ b/akonadi/tests/fakemonitor.cpp @@ -0,0 +1,27 @@ +/* + Copyright (c) 2009 Stephen Kelly + + 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 "fakemonitor.h" + + +FakeMonitor::FakeMonitor(QObject* parent): Monitor(parent) +{ + +} diff --git a/akonadi/tests/fakemonitor.h b/akonadi/tests/fakemonitor.h new file mode 100644 index 000000000..91920ad65 --- /dev/null +++ b/akonadi/tests/fakemonitor.h @@ -0,0 +1,34 @@ +/* + Copyright (c) 2009 Stephen Kelly + + 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 FAKEMONITOR_H +#define FAKEMONITOR_H + +#include "monitor.h" + +class FakeMonitor : public Akonadi::Monitor +{ + Q_OBJECT +public: + FakeMonitor(QObject* parent = 0); + +}; + +#endif + diff --git a/akonadi/tests/fakeserver.cpp b/akonadi/tests/fakeserver.cpp new file mode 100644 index 000000000..805fed345 --- /dev/null +++ b/akonadi/tests/fakeserver.cpp @@ -0,0 +1,102 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Stephen Kelly + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +// Own +#include "fakeserver.h" + +// Qt +#include + +// KDE +#include + +// For unlink +# include + +FakeAkonadiServer::FakeAkonadiServer( QObject* parent ) : QThread( parent ) +{ + moveToThread(this); +} + + +FakeAkonadiServer::~FakeAkonadiServer() +{ + quit(); + wait(); +} + +void FakeAkonadiServer::dataAvailable() +{ + QMutexLocker locker(&m_mutex); + + QByteArray data = m_localSocket->readAll(); + + if (data.startsWith("0 LOGIN")) + { + m_localSocket->write( "0 OK User logged in\r\n" ); + return; + } + + Q_ASSERT( !m_data.isEmpty() ); + + QByteArray toWrite = QString( m_data.takeFirst() + "\r\n" ).toLatin1(); + + Q_FOREVER { + m_localSocket->write( toWrite ); + if (toWrite.startsWith("* ")) { + toWrite = QString( m_data.takeFirst() + "\r\n" ).toLatin1(); + } else + break; + } +} + +void FakeAkonadiServer::newConnection() +{ + QMutexLocker locker(&m_mutex); + + m_localSocket = m_localServer->nextPendingConnection(); + m_localSocket->write( QByteArray( "* OK Akonadi Fake Server [PROTOCOL 17]\r\n" ) ); + connect(m_localSocket, SIGNAL(readyRead()), this, SLOT(dataAvailable())); +} + +void FakeAkonadiServer::run() +{ + m_localServer = new QLocalServer(); + const QString socketFile = QLatin1String( "/tmp/akonadi-test/fakeakonadiserver.socket" ); + unlink( socketFile.toUtf8().constData() ); + if ( !m_localServer->listen( socketFile ) ) { + kFatal() << "Unable to start the server"; + } + + connect(m_localServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + + m_data += QString("Foo"); + exec(); + disconnect(m_localSocket, SIGNAL(readyRead()), this, SLOT(dataAvailable())); + delete m_localServer; +} + +void FakeAkonadiServer::setResponse( const QStringList& data ) +{ + QMutexLocker locker(&m_mutex); + + m_data+= data; +} + +// #include "fakeserver.moc" diff --git a/akonadi/tests/fakeserver.h b/akonadi/tests/fakeserver.h new file mode 100644 index 000000000..b2b496c5c --- /dev/null +++ b/akonadi/tests/fakeserver.h @@ -0,0 +1,54 @@ +/* + Copyright (C) 2008 Omat Holding B.V. + Copyright (C) 2009 Stephen Kelly + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef FAKE_AKONADI_SERVER_H +#define FAKE_AKONADI_SERVER_H + +// Renamed and refactored from kdepimlibs/kimap/tests/fakeserver + +#include +#include +#include +#include +#include +#include + +class FakeAkonadiServer : public QThread +{ + Q_OBJECT + +public: + FakeAkonadiServer( QObject* parent = 0 ); + ~FakeAkonadiServer(); + virtual void run(); + void setResponse( const QStringList& data ); + +private Q_SLOTS: + void newConnection(); + void dataAvailable(); + +private: + QStringList m_data; + QLocalServer *m_localServer; + QMutex m_mutex; + QLocalSocket* m_localSocket; +}; + +#endif + diff --git a/akonadi/tests/fakesession.cpp b/akonadi/tests/fakesession.cpp new file mode 100644 index 000000000..52efb8f57 --- /dev/null +++ b/akonadi/tests/fakesession.cpp @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009 Stephen Kelly + + 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 "fakesession.h" +#include "session_p.h" + +#include + +FakeSession::FakeSession(const QByteArray& sessionId, QObject* parent) + : Session(sessionId, parent) +{ + +} + +void FakeSession::firstListJobResult(QList collectionChunks) +{ + // write the collection chunks to the socket. +} + + + diff --git a/akonadi/tests/fakesession.h b/akonadi/tests/fakesession.h new file mode 100755 index 000000000..1d1fab1c1 --- /dev/null +++ b/akonadi/tests/fakesession.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2009 Stephen Kelly + + 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 FAKESESSION_H +#define FAKESESSION_H + +#include "session.h" +#include "collection.h" + + +using namespace Akonadi; + +class FakeSession : public Session +{ + Q_OBJECT +public: + explicit FakeSession(const QByteArray& sessionId = QByteArray(), QObject* parent = 0); + + void firstListJobResult(QList collectionChunks); + +}; + +#endif diff --git a/includes/Akonadi/TransactionJobs b/includes/Akonadi/TransactionBeginJob similarity index 100% copy from includes/Akonadi/TransactionJobs copy to includes/Akonadi/TransactionBeginJob diff --git a/includes/Akonadi/TransactionJobs b/includes/Akonadi/TransactionCommitJob similarity index 100% copy from includes/Akonadi/TransactionJobs copy to includes/Akonadi/TransactionCommitJob diff --git a/includes/Akonadi/TransactionJobs b/includes/Akonadi/TransactionRollbackJob similarity index 100% rename from includes/Akonadi/TransactionJobs rename to includes/Akonadi/TransactionRollbackJob diff --git a/includes/KCal/HtmlExportSettings b/includes/KCal/HTMLExportSettings similarity index 100% rename from includes/KCal/HtmlExportSettings rename to includes/KCal/HTMLExportSettings diff --git a/includes/KCal/KCalVersion b/includes/KCal/KCalVersion deleted file mode 100644 index d0d2cf87a..000000000 --- a/includes/KCal/KCalVersion +++ /dev/null @@ -1 +0,0 @@ -#include "../../kcal/kcalversion.h" diff --git a/includes/KCal/ResourceCachedConfig b/includes/KCal/ResourceCachedReloadConfig similarity index 100% copy from includes/KCal/ResourceCachedConfig copy to includes/KCal/ResourceCachedReloadConfig diff --git a/includes/KCal/ResourceCachedConfig b/includes/KCal/ResourceCachedSaveConfig similarity index 100% rename from includes/KCal/ResourceCachedConfig rename to includes/KCal/ResourceCachedSaveConfig diff --git a/includes/KIMAP/GetMetadataJob b/includes/KIMAP/GetMetaDataJob similarity index 100% rename from includes/KIMAP/GetMetadataJob rename to includes/KIMAP/GetMetaDataJob diff --git a/includes/KIMAP/MetadataJobBase b/includes/KIMAP/MetaDataJobBase similarity index 100% rename from includes/KIMAP/MetadataJobBase rename to includes/KIMAP/MetaDataJobBase diff --git a/includes/KIMAP/RFCCodecs b/includes/KIMAP/RFCCodecs deleted file mode 100644 index 50169c4fc..000000000 --- a/includes/KIMAP/RFCCodecs +++ /dev/null @@ -1 +0,0 @@ -#include "../../kimap/rfccodecs.h" diff --git a/includes/KIMAP/SetMetadataJob b/includes/KIMAP/SetMetaDataJob similarity index 100% rename from includes/KIMAP/SetMetadataJob rename to includes/KIMAP/SetMetaDataJob diff --git a/includes/KLDAP/LdapDefs b/includes/KLDAP/LdapDefs deleted file mode 100644 index c4cfcca32..000000000 --- a/includes/KLDAP/LdapDefs +++ /dev/null @@ -1 +0,0 @@ -#include "../../kldap/ldapdefs.h" diff --git a/includes/KMime/KMimeCharFreq b/includes/KMime/CharFreq similarity index 100% rename from includes/KMime/KMimeCharFreq rename to includes/KMime/CharFreq diff --git a/includes/KMime/KMimeCodecs b/includes/KMime/Codecs similarity index 100% rename from includes/KMime/KMimeCodecs rename to includes/KMime/Codecs diff --git a/includes/KMime/KMimeContent b/includes/KMime/Content similarity index 100% rename from includes/KMime/KMimeContent rename to includes/KMime/Content diff --git a/includes/KMime/KMimeContentIndex b/includes/KMime/ContentIndex similarity index 100% rename from includes/KMime/KMimeContentIndex rename to includes/KMime/ContentIndex diff --git a/includes/KMime/KMimeDateFormatter b/includes/KMime/DateFormatter similarity index 100% rename from includes/KMime/KMimeDateFormatter rename to includes/KMime/DateFormatter diff --git a/includes/KMime/KMimeHeaderParsing b/includes/KMime/HeaderParsing similarity index 100% rename from includes/KMime/KMimeHeaderParsing rename to includes/KMime/HeaderParsing diff --git a/includes/KMime/KMimeHeaders b/includes/KMime/Headers similarity index 100% rename from includes/KMime/KMimeHeaders rename to includes/KMime/Headers diff --git a/includes/KMime/KMimeUtil b/includes/KMime/KMimeUtil deleted file mode 100644 index d859241a9..000000000 --- a/includes/KMime/KMimeUtil +++ /dev/null @@ -1 +0,0 @@ -#include "../../kmime/kmime_util.h" diff --git a/includes/KMime/KMimeMDN b/includes/KMime/MDN similarity index 100% rename from includes/KMime/KMimeMDN rename to includes/KMime/MDN diff --git a/includes/KMime/Message b/includes/KMime/Message new file mode 100644 index 000000000..ab133146a --- /dev/null +++ b/includes/KMime/Message @@ -0,0 +1 @@ +#include "../../kmime/kmime_message.h" diff --git a/includes/KMime/KMimeNewsArticle b/includes/KMime/NewsArticle similarity index 100% rename from includes/KMime/KMimeNewsArticle rename to includes/KMime/NewsArticle diff --git a/includes/KPIMUtils/KFileIO b/includes/KPIMUtils/KFileIO deleted file mode 100644 index 1aaf83c17..000000000 --- a/includes/KPIMUtils/KFileIO +++ /dev/null @@ -1 +0,0 @@ -#include "../../kpimutils/kfileio.h" diff --git a/includes/KPIMUtils/SuperTrait b/includes/KPIMUtils/SuperTrait deleted file mode 100644 index c07076348..000000000 --- a/includes/KPIMUtils/SuperTrait +++ /dev/null @@ -1 +0,0 @@ -#include "../../kpimutils/supertrait.h" diff --git a/includes/KTNEF/KTNEFDefs b/includes/KTNEF/KTNEFDefs deleted file mode 100644 index 1b97a551e..000000000 --- a/includes/KTNEF/KTNEFDefs +++ /dev/null @@ -1 +0,0 @@ -#include "../../ktnef/ktnefdefs.h" diff --git a/includes/KTNEF/KTNEFTWriter b/includes/KTNEF/KTNEFWriter similarity index 100% rename from includes/KTNEF/KTNEFTWriter rename to includes/KTNEF/KTNEFWriter diff --git a/includes/Mailtransport/SMTPJob b/includes/Mailtransport/SmtpJob similarity index 100% rename from includes/Mailtransport/SMTPJob rename to includes/Mailtransport/SmtpJob diff --git a/includes/Mailtransport/TransportCombobox b/includes/Mailtransport/TransportComboBox similarity index 100% rename from includes/Mailtransport/TransportCombobox rename to includes/Mailtransport/TransportComboBox diff --git a/kabc/CMakeLists.txt b/kabc/CMakeLists.txt index 08b868e3e..b15d26d65 100644 --- a/kabc/CMakeLists.txt +++ b/kabc/CMakeLists.txt @@ -1,138 +1,136 @@ project(kabc) include_directories( ${KDE4_KIO_INCLUDES} ) add_definitions(${QDBUS_DEFINITIONS} -DKDE_DEFAULT_DEBUG_AREA=5700) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) # these apply also for all subdirs include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/kab - ${CMAKE_CURRENT_BINARY_DIR}/kab ${CMAKE_CURRENT_SOURCE_DIR}/vcardparser ) # kabc/vcardparser/Makefile.am: vcards set(vcards_STAT_SRCS vcardparser/vcard.cpp vcardparser/vcardline.cpp vcardparser/vcardparser.cpp ) add_subdirectory( vcardparser ) add_subdirectory( formats ) add_subdirectory( plugins ) add_subdirectory( tests ) #add_subdirectory( scripts ) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/scripts) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/field.cpp ${CMAKE_CURRENT_BINARY_DIR}/addressee.h ${CMAKE_CURRENT_BINARY_DIR}/addressee.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/scripts COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/makeaddressee DEPENDS scripts/makeaddressee scripts/addressee.src.cpp scripts/addressee.src.h scripts/entrylist scripts/field.src.cpp ) ########### next target ############### set(kabc_LIB_SRCS address.cpp addressbook.cpp ${CMAKE_CURRENT_BINARY_DIR}/addressee.cpp ${CMAKE_CURRENT_BINARY_DIR}/addressee.h ${CMAKE_CURRENT_BINARY_DIR}/field.cpp addresseedialog.cpp distributionlist.cpp distributionlistdialog.cpp emailselectdialog.cpp errorhandler.cpp formatfactory.cpp geo.cpp key.cpp phonenumber.cpp picture.cpp plugin.cpp resource.cpp resourceabc.cpp resourcecached.cpp secrecy.cpp sound.cpp stdaddressbook.cpp vcardconverter.cpp timezone.cpp vcardformat.cpp ldifconverter.cpp addresslineedit.cpp addresseelist.cpp vcardtool.cpp addresseehelper.cpp lock.cpp locknull.cpp sortmode.cpp ${vcards_STAT_SRCS} contactgroup.cpp contactgrouptool.cpp ) kde4_add_library(kabc SHARED ${kabc_LIB_SRCS}) target_link_libraries(kabc kresources kldap ${KDE4_KDEUI_LIBS} ${KDE4_KDECORE_LIBS}) target_link_libraries(kabc LINK_INTERFACE_LIBRARIES kresources ${KDE4_KDEUI_LIBS}) set_target_properties(kabc PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install(TARGETS kabc EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES countrytransl.map DESTINATION ${DATA_INSTALL_DIR}/kabc ) install( FILES kabc_manager.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kresources ) install( FILES kabc_export.h address.h addressbook.h ${CMAKE_CURRENT_BINARY_DIR}/addressee.h addresseelist.h addresseedialog.h addresslineedit.h contactgroup.h contactgrouptool.h distributionlist.h distributionlistdialog.h emailselectdialog.h errorhandler.h field.h format.h formatfactory.h geo.h key.h ldifconverter.h lock.h locknull.h phonenumber.h picture.h plugin.h resource.h resourceabc.h resourcecached.h secrecy.h sortmode.h sound.h stdaddressbook.h timezone.h vcardformat.h vcardconverter.h DESTINATION ${INCLUDE_INSTALL_DIR}/kabc COMPONENT Devel) diff --git a/kabc/addresseedialog.cpp b/kabc/addresseedialog.cpp index 3e6a5310c..01020412d 100644 --- a/kabc/addresseedialog.cpp +++ b/kabc/addresseedialog.cpp @@ -1,338 +1,337 @@ /* This file is part of libkabc. Copyright (c) 2001 Cornelius Schumacher 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 "addresseedialog.h" #include "stdaddressbook.h" #include #include #include #include #include #include using namespace KABC; class AddresseeItem::Private { public: Addressee mAddressee; }; AddresseeItem::AddresseeItem( QTreeWidget *parent, const Addressee &addressee ) : QTreeWidgetItem( parent ), d( new Private ) { d->mAddressee = addressee; setText( Name, addressee.realName() ); setText( Email, addressee.preferredEmail() ); } AddresseeItem::~AddresseeItem() { delete d; } Addressee AddresseeItem::addressee() const { return d->mAddressee; } QString AddresseeItem::key( int column, bool ) const { if ( column == Email ) { QString value = text( Email ); QRegExp emailRe( QLatin1String( "<\\S*>" ) ); int match = emailRe.indexIn( value ); if ( match > -1 ) { value = value.mid( match + 1, emailRe.matchedLength() - 2 ); } return value.toLower(); } return text( column ).toLower(); } class AddresseeDialog::Private { public: Private( bool multiple ) : mMultiple( multiple ) { } void addressBookChanged(); void selectItem( const QString & ); void updateEdit(); void addSelected( QTreeWidgetItem *item ); void removeSelected(); void loadAddressBook(); void addCompletionItem( const QString &str, QTreeWidgetItem *item ); bool mMultiple; QTreeWidget *mAddresseeList; KLineEdit *mAddresseeEdit; QTreeWidget *mSelectedList; AddressBook *mAddressBook; QHash mItemDict; QHash mSelectedDict; }; AddresseeDialog::AddresseeDialog( QWidget *parent, bool multiple ) : KDialog( parent ), d( new Private( multiple ) ) { setCaption( i18nc( "@title:window", "Select Addressee" ) ); setButtons( Ok | Cancel ); setDefaultButton( Ok ); QWidget *topWidget = new QWidget( this ); setMainWidget( topWidget ); QBoxLayout *topLayout = new QHBoxLayout( topWidget ); QBoxLayout *listLayout = new QVBoxLayout; topLayout->addLayout( listLayout ); d->mAddresseeList = new QTreeWidget( topWidget ); d->mAddresseeList->setColumnCount( 2 ); QStringList headerTitles; headerTitles << i18nc( "@title:column addressee name", "Name" ) << i18nc( "@title:column addressee email", "Email" ); d->mAddresseeList->setHeaderItem( new QTreeWidgetItem( headerTitles ) ); listLayout->addWidget( d->mAddresseeList ); connect( d->mAddresseeList, SIGNAL( itemDoubleClicked( QTreeWidgetItem *, int ) ), SLOT( accept() ) ); connect( d->mAddresseeList, SIGNAL( itemSelectionChanged() ), SLOT( updateEdit() ) ); d->mAddresseeEdit = new KLineEdit( topWidget ); d->mAddresseeEdit->setCompletionMode( KGlobalSettings::CompletionAuto ); connect( d->mAddresseeEdit->completionObject(), SIGNAL( match( const QString & ) ), SLOT( selectItem( const QString & ) ) ); d->mAddresseeEdit->setFocus(); d->mAddresseeEdit->completionObject()->setIgnoreCase( true ); listLayout->addWidget( d->mAddresseeEdit ); setInitialSize( QSize( 450, 300 ) ); if ( d->mMultiple ) { QBoxLayout *selectedLayout = new QVBoxLayout; topLayout->addLayout( selectedLayout ); - topLayout->setSpacing( spacingHint() ); QGroupBox *selectedGroup = new QGroupBox( i18nc( "@title:group selected addressees", "Selected" ), topWidget ); QHBoxLayout *groupLayout = new QHBoxLayout; selectedGroup->setLayout( groupLayout ); selectedLayout->addWidget( selectedGroup ); d->mSelectedList = new QTreeWidget( selectedGroup ); groupLayout->addWidget( d->mSelectedList ); d->mSelectedList->setColumnCount( 2 ); QStringList headerTitles; headerTitles << i18nc( "@title:column addressee name", "Name" ) << i18nc( "@title:column addressee email", "Email" ); d->mSelectedList->setHeaderItem( new QTreeWidgetItem( headerTitles ) ); connect( d->mSelectedList, SIGNAL( itemDoubleClicked( QTreeWidgetItem *, int ) ), SLOT( removeSelected() ) ); QPushButton *unselectButton = new QPushButton( i18nc( "@action:button unselect addressee", "Unselect" ), selectedGroup ); selectedLayout->addWidget( unselectButton ); connect( unselectButton, SIGNAL( clicked() ), SLOT( removeSelected() ) ); connect( d->mAddresseeList, SIGNAL( itemClicked( QTreeWidgetItem *, int ) ), SLOT( addSelected( QTreeWidgetItem * ) ) ); setInitialSize( QSize( 650, 350 ) ); } d->mAddressBook = StdAddressBook::self( true ); connect( d->mAddressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( addressBookChanged() ) ); connect( d->mAddressBook, SIGNAL( loadingFinished( Resource* ) ), SLOT( addressBookChanged() ) ); d->loadAddressBook(); } AddresseeDialog::~AddresseeDialog() { delete d; } Addressee AddresseeDialog::addressee() const { AddresseeItem *aItem = 0; if ( d->mMultiple ) { aItem = dynamic_cast( d->mSelectedList->topLevelItem( 0 ) ); } else { QList selected = d->mAddresseeList->selectedItems(); if ( selected.count() != 0 ) { aItem = dynamic_cast( selected.at( 0 ) ); } } if ( aItem ) { return aItem->addressee(); } return Addressee(); } Addressee::List AddresseeDialog::addressees() const { Addressee::List al; AddresseeItem *aItem = 0; if ( d->mMultiple ) { for ( int i = 0; i < d->mSelectedList->topLevelItemCount(); ++i ) { aItem = dynamic_cast( d->mSelectedList->topLevelItem( i ) ); if ( aItem ) { al.append( aItem->addressee() ); } } } else { QList selected = d->mAddresseeList->selectedItems(); if ( selected.count() != 0 ) { aItem = dynamic_cast( selected.at( 0 ) ); } if ( aItem ) { al.append( aItem->addressee() ); } } return al; } Addressee AddresseeDialog::getAddressee( QWidget *parent ) { AddresseeDialog dlg( parent ); if ( dlg.exec() ) { return dlg.addressee(); } return Addressee(); } Addressee::List AddresseeDialog::getAddressees( QWidget *parent ) { AddresseeDialog dlg( parent, true ); if ( dlg.exec() ) { return dlg.addressees(); } return Addressee::List(); } void AddresseeDialog::Private::loadAddressBook() { mAddresseeList->clear(); mItemDict.clear(); mAddresseeEdit->completionObject()->clear(); AddressBook::Iterator it; for ( it = mAddressBook->begin(); it != mAddressBook->end(); ++it ) { AddresseeItem *item = new AddresseeItem( mAddresseeList, (*it) ); addCompletionItem( (*it).realName(), item ); addCompletionItem( (*it).preferredEmail(), item ); } } void AddresseeDialog::Private::addCompletionItem( const QString &str, QTreeWidgetItem *item ) { if ( str.isEmpty() ) { return; } mItemDict.insert( str, item ); mAddresseeEdit->completionObject()->addItem( str ); } void AddresseeDialog::Private::selectItem( const QString &str ) { if ( str.isEmpty() ) { return; } QTreeWidgetItem *item = mItemDict.value( str, 0 ); if ( item ) { mAddresseeList->blockSignals( true ); mAddresseeList->setItemSelected( item, true ); mAddresseeList->scrollToItem( item ); mAddresseeList->blockSignals( false ); } } void AddresseeDialog::Private::updateEdit() { QList selected = mAddresseeList->selectedItems(); if ( selected.count() == 0 ) { return; } QTreeWidgetItem *item = selected.at( 0 ); mAddresseeEdit->setText( item->text( 0 ) ); mAddresseeEdit->setSelection( 0, item->text( 0 ).length() ); } void AddresseeDialog::Private::addSelected( QTreeWidgetItem *item ) { AddresseeItem *addrItem = dynamic_cast( item ); if ( !addrItem ) { return; } Addressee a = addrItem->addressee(); QTreeWidgetItem *selectedItem = mSelectedDict.value( a.uid(), 0 ); if ( !selectedItem ) { selectedItem = new AddresseeItem( mSelectedList, a ); mSelectedDict.insert( a.uid(), selectedItem ); } } void AddresseeDialog::Private::removeSelected() { QList selected = mSelectedList->selectedItems(); if ( selected.count() == 0 ) { return; } AddresseeItem *addrItem = dynamic_cast( selected.at( 0 ) ); if ( !addrItem ) { return; } mSelectedDict.remove( addrItem->addressee().uid() ); delete addrItem; } void AddresseeDialog::Private::addressBookChanged() { loadAddressBook(); } #include "addresseedialog.moc" diff --git a/kabc/distributionlistdialog.cpp b/kabc/distributionlistdialog.cpp index 816c575d9..dace97959 100644 --- a/kabc/distributionlistdialog.cpp +++ b/kabc/distributionlistdialog.cpp @@ -1,482 +1,481 @@ /* This file is part of libkabc. Copyright (c) 2001 Cornelius Schumacher 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 "distributionlistdialog.h" #include "distributionlist.h" #include "addressbook.h" #include "addresseedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KABC; DistributionListDialog::DistributionListDialog( AddressBook *addressBook, QWidget *parent ) : KDialog( parent ), d( 0 ) { setModal( true ); setCaption( i18n( "Configure Distribution Lists" ) ); setButtons( Ok ); setDefaultButton( Ok ); showButtonSeparator( true ); DistributionListEditorWidget *editor = new DistributionListEditorWidget( addressBook, this ); setMainWidget( editor ); connect( this, SIGNAL( okClicked() ), editor, SLOT( save() ) ); } DistributionListDialog::~DistributionListDialog() { } class EmailSelector::Private { public: QButtonGroup *mButtonGroup; QMap mEmailMap; }; EmailSelector::EmailSelector( const QStringList &emails, const QString ¤t, QWidget *parent ) : KDialog( parent ), d( new Private ) { setCaption( i18n( "Select Email Address" ) ); setButtons( Ok ); setDefaultButton( Ok ); QFrame *topFrame = new QFrame( this ); setMainWidget( topFrame ); QBoxLayout *topLayout = new QVBoxLayout( topFrame ); QGroupBox *box = new QGroupBox( i18n( "Email Addresses" ) ); d->mButtonGroup = new QButtonGroup( box ); topLayout->addWidget( box ); QVBoxLayout *layout = new QVBoxLayout; QStringList::ConstIterator it; for ( it = emails.begin(); it != emails.end(); ++it ) { QRadioButton *button = new QRadioButton( *it, box ); d->mButtonGroup->addButton( button ); d->mEmailMap.insert( button, *it ); layout->addWidget( button ); if ( (*it) == current ) { button->setChecked( true ); } } layout->addStretch( 1 ); box->setLayout( layout ); } EmailSelector::~EmailSelector() { delete d; } QString EmailSelector::selected() const { QAbstractButton *button = d->mButtonGroup->checkedButton(); if ( !button ) { return QString(); } return d->mEmailMap[button]; } QString EmailSelector::getEmail( const QStringList &emails, const QString ¤t, QWidget *parent ) { EmailSelector dlg( emails, current, parent ); dlg.exec(); return dlg.selected(); } class EntryItem : public QTreeWidgetItem { public: EntryItem( QTreeWidget *parent, const Addressee &addressee, const QString &email=QString() ) : QTreeWidgetItem( parent ), mAddressee( addressee ), mEmail( email ) { setText( 0, addressee.realName() ); if ( email.isEmpty() ) { setText( 1, addressee.preferredEmail() ); setText( 2, i18nc( "this the preferred email address", "Yes" ) ); } else { setText( 1, email ); setText( 2, i18nc( "this is not the preferred email address", "No" ) ); } } Addressee addressee() const { return mAddressee; } QString email() const { return mEmail; } private: Addressee mAddressee; QString mEmail; }; class DistributionListEditorWidget::Private { public: Private( AddressBook *addressBook, DistributionListEditorWidget *parent ) : mParent( parent ), mAddressBook( addressBook ) { } ~Private() { } void newList(); void editList(); void removeList(); void addEntry(); void removeEntry(); void changeEmail(); void updateEntryView(); void updateAddresseeView(); void updateNameCombo(); void slotSelectionEntryViewChanged(); void slotSelectionAddresseeViewChanged(); void save(); DistributionListEditorWidget *mParent; KComboBox *mNameCombo; QLabel *mListLabel; QTreeWidget *mEntryView; QTreeWidget *mAddresseeView; AddressBook *mAddressBook; QPushButton *mNewButton, *mEditButton, *mRemoveButton; QPushButton *mChangeEmailButton, *mRemoveEntryButton, *mAddEntryButton; }; DistributionListEditorWidget::DistributionListEditorWidget( AddressBook *addressBook, QWidget *parent ) : QWidget( parent ), d( new Private( addressBook, this ) ) { kDebug(); QBoxLayout *topLayout = new QVBoxLayout( this ); - topLayout->setSpacing( KDialog::spacingHint() ); QBoxLayout *nameLayout = new QHBoxLayout(); topLayout->addLayout( topLayout ); d->mNameCombo = new KComboBox( this ); nameLayout->addWidget( d->mNameCombo ); connect( d->mNameCombo, SIGNAL( activated( int ) ), SLOT( updateEntryView() ) ); d->mNewButton = new QPushButton( i18n( "New List..." ), this ); nameLayout->addWidget( d->mNewButton ); connect( d->mNewButton, SIGNAL( clicked() ), SLOT( newList() ) ); d->mEditButton = new QPushButton( i18n( "Rename List..." ), this ); nameLayout->addWidget( d->mEditButton ); connect( d->mEditButton, SIGNAL( clicked() ), SLOT( editList() ) ); d->mRemoveButton = new QPushButton( i18n( "Remove List" ), this ); nameLayout->addWidget( d->mRemoveButton ); connect( d->mRemoveButton, SIGNAL( clicked() ), SLOT( removeList() ) ); QGridLayout *gridLayout = new QGridLayout(); topLayout->addLayout( gridLayout ); gridLayout->setColumnStretch( 1, 1 ); QLabel *listLabel = new QLabel( i18n( "Available addresses:" ), this ); gridLayout->addWidget( listLabel, 0, 0 ); d->mListLabel = new QLabel( this ); gridLayout->addWidget( d->mListLabel, 0, 0, 1, 2 ); d->mAddresseeView = new QTreeWidget( this ); d->mAddresseeView->setColumnCount( 2 ); QStringList labels; labels << i18nc( "@title:column addressee name", "Name" ) << i18nc( "@title:column addressee preferred email", "Preferred Email" ); d->mAddresseeView->setHeaderLabels( labels ); gridLayout->addWidget( d->mAddresseeView, 1, 0 ); connect( d->mAddresseeView, SIGNAL( itemSelectionChanged() ), SLOT( slotSelectionAddresseeViewChanged() ) ); connect( d->mAddresseeView, SIGNAL( itemDoubleClicked( QTreeWidgetItem *, int ) ), SLOT( addEntry() ) ); d->mAddEntryButton = new QPushButton( i18n( "Add Entry" ), this ); d->mAddEntryButton->setEnabled( false ); gridLayout->addWidget( d->mAddEntryButton, 2, 0 ); connect( d->mAddEntryButton, SIGNAL( clicked() ), SLOT( addEntry() ) ); d->mEntryView = new QTreeWidget( this ); QStringList entryLabels; entryLabels << i18nc( "@title:column addressee name", "Name" ) << i18nc( "@title:column addressee preferred email", "Email" ) << i18nc( "@title:column use preferred email", "Use Preferred" ); d->mEntryView->setEnabled( false ); gridLayout->addWidget( d->mEntryView, 1, 1, 1, 2 ); connect( d->mEntryView, SIGNAL( itemSelectionChanged() ), SLOT( slotSelectionEntryViewChanged() ) ); d->mChangeEmailButton = new QPushButton( i18n( "Change Email..." ), this ); gridLayout->addWidget( d->mChangeEmailButton, 2, 1 ); connect( d->mChangeEmailButton, SIGNAL( clicked() ), SLOT( changeEmail() ) ); d->mRemoveEntryButton = new QPushButton( i18n( "Remove Entry" ), this ); gridLayout->addWidget( d->mRemoveEntryButton, 2, 2 ); connect( d->mRemoveEntryButton, SIGNAL( clicked() ), SLOT( removeEntry() ) ); d->updateAddresseeView(); d->updateNameCombo(); } DistributionListEditorWidget::~DistributionListEditorWidget() { delete d; } void DistributionListEditorWidget::Private::save() { // FIXME new distribution list handling // do we need extra save? //mManager->save(); } void DistributionListEditorWidget::Private::slotSelectionEntryViewChanged() { QList selected = mEntryView->selectedItems(); bool state = selected.count() > 0; mChangeEmailButton->setEnabled( state ); mRemoveEntryButton->setEnabled( state ); } void DistributionListEditorWidget::Private::newList() { bool ok; QString name = KInputDialog::getText( i18n( "New Distribution List" ), i18n( "Please enter &name:" ), QString(), &ok ); if ( !ok ) { return; } mAddressBook->createDistributionList( name ); mNameCombo->clear(); mNameCombo->addItems( mAddressBook->allDistributionListNames() ); mNameCombo->setCurrentIndex( mNameCombo->count() - 1 ); updateEntryView(); slotSelectionAddresseeViewChanged(); } void DistributionListEditorWidget::Private::editList() { QString oldName = mNameCombo->currentText(); bool ok; QString name = KInputDialog::getText( i18n( "Distribution List" ), i18n( "Please change &name:" ), oldName, &ok ); if ( !ok ) { return; } DistributionList *list = mAddressBook->findDistributionListByName( oldName ); if ( list ) { list->setName( name ); } mNameCombo->clear(); mNameCombo->addItems( mAddressBook->allDistributionListNames() ); mNameCombo->setCurrentIndex( mNameCombo->count() - 1 ); updateEntryView(); slotSelectionAddresseeViewChanged(); } void DistributionListEditorWidget::Private::removeList() { int result = KMessageBox::warningContinueCancel( mParent, i18n( "Delete distribution list '%1'?", mNameCombo->currentText() ), QString(), KStandardGuiItem::del() ); if ( result != KMessageBox::Continue ) { return; } DistributionList *list = mAddressBook->findDistributionListByName( mNameCombo->currentText() ); if ( list ) { // FIXME new distribution list handling // list should be deleted, no? mAddressBook->removeDistributionList( list ); mNameCombo->removeItem( mNameCombo->currentIndex() ); } updateEntryView(); slotSelectionAddresseeViewChanged(); } void DistributionListEditorWidget::Private::addEntry() { QList selected = mAddresseeView->selectedItems(); if ( selected.count() == 0 ) { kDebug() << "No addressee selected."; return; } AddresseeItem *addresseeItem = static_cast( selected.at( 0 ) ); DistributionList *list = mAddressBook->findDistributionListByName( mNameCombo->currentText() ); if ( !list ) { kDebug() << "No dist list '" << mNameCombo->currentText() << "'"; return; } list->insertEntry( addresseeItem->addressee() ); updateEntryView(); slotSelectionAddresseeViewChanged(); } void DistributionListEditorWidget::Private::removeEntry() { DistributionList *list = mAddressBook->findDistributionListByName( mNameCombo->currentText() ); if ( !list ) { return; } QList selected = mEntryView->selectedItems(); if ( selected.count() == 0 ) { return; } EntryItem *entryItem = static_cast( selected.at( 0 ) ); list->removeEntry( entryItem->addressee(), entryItem->email() ); delete entryItem; } void DistributionListEditorWidget::Private::changeEmail() { DistributionList *list = mAddressBook->findDistributionListByName( mNameCombo->currentText() ); if ( !list ) { return; } QList selected = mEntryView->selectedItems(); if ( selected.count() == 0 ) { return; } EntryItem *entryItem = static_cast( selected.at( 0 ) ); QString email = EmailSelector::getEmail( entryItem->addressee().emails(), entryItem->email(), mParent ); list->removeEntry( entryItem->addressee(), entryItem->email() ); list->insertEntry( entryItem->addressee(), email ); updateEntryView(); } void DistributionListEditorWidget::Private::updateEntryView() { if ( mNameCombo->currentText().isEmpty() ) { mListLabel->setText( i18n( "Selected addressees:" ) ); } else { mListLabel->setText( i18n( "Selected addresses in '%1':", mNameCombo->currentText() ) ); } mEntryView->clear(); DistributionList *list = mAddressBook->findDistributionListByName( mNameCombo->currentText() ); if ( !list ) { mEditButton->setEnabled( false ); mRemoveButton->setEnabled( false ); mChangeEmailButton->setEnabled( false ); mRemoveEntryButton->setEnabled( false ); mAddresseeView->setEnabled( false ); mEntryView->setEnabled( false ); return; } else { mEditButton->setEnabled( true ); mRemoveButton->setEnabled( true ); mAddresseeView->setEnabled( true ); mEntryView->setEnabled( true ); } DistributionList::Entry::List entries = list->entries(); DistributionList::Entry::List::ConstIterator it; for ( it = entries.constBegin(); it != entries.constEnd(); ++it ) { new EntryItem( mEntryView, (*it).addressee(), (*it).email() ); } QList selected = mEntryView->selectedItems(); bool state = ( selected.count() != 0 ); mChangeEmailButton->setEnabled( state ); mRemoveEntryButton->setEnabled( state ); } void DistributionListEditorWidget::Private::updateAddresseeView() { mAddresseeView->clear(); AddressBook::Iterator it; for ( it = mAddressBook->begin(); it != mAddressBook->end(); ++it ) { new AddresseeItem( mAddresseeView, *it ); } } void DistributionListEditorWidget::Private::updateNameCombo() { mNameCombo->addItems( mAddressBook->allDistributionListNames() ); updateEntryView(); } void DistributionListEditorWidget::Private::slotSelectionAddresseeViewChanged() { QList selected = mAddresseeView->selectedItems(); bool state = ( selected.count() != 0 ); mAddEntryButton->setEnabled( state && !mNameCombo->currentText().isEmpty() ); } #include "distributionlistdialog.moc" diff --git a/kabc/plugins/dir/resourcedirconfig.cpp b/kabc/plugins/dir/resourcedirconfig.cpp index 19d95e1a9..5c7e55623 100644 --- a/kabc/plugins/dir/resourcedirconfig.cpp +++ b/kabc/plugins/dir/resourcedirconfig.cpp @@ -1,110 +1,104 @@ /* This file is part of libkabc. Copyright (c) 2002 Tobias Koenig 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 "resourcedirconfig.h" #include "resourcedir.h" #include "kabc/formatfactory.h" #include "kabc/stdaddressbook.h" #include #include #include #include -#include -#include +#include using namespace KABC; ResourceDirConfig::ResourceDirConfig( QWidget *parent ) : KRES::ConfigWidget( parent ) { - QGridLayout *mainLayout = new QGridLayout( this ); + QFormLayout *mainLayout = new QFormLayout( this ); mainLayout->setMargin( 0 ); - mainLayout->setSpacing( KDialog::spacingHint() ); - QLabel *label = new QLabel( i18n( "Format:" ), this ); mFormatBox = new KComboBox( this ); - mainLayout->addWidget( label, 0, 0 ); - mainLayout->addWidget( mFormatBox, 0, 1 ); + mainLayout->addRow( i18n( "Format:" ), mFormatBox ); - label = new QLabel( i18n( "Location:" ), this ); mFileNameEdit = new KUrlRequester( this ); mFileNameEdit->setMode( KFile::Directory ); - mainLayout->addWidget( label, 1, 0 ); - mainLayout->addWidget( mFileNameEdit, 1, 1 ); + mainLayout->addRow( i18n( "Location:" ), mFileNameEdit ); FormatFactory *factory = FormatFactory::self(); QStringList formats = factory->formats(); QStringList::Iterator it; for ( it = formats.begin(); it != formats.end(); ++it ) { FormatInfo info = factory->info( *it ); if ( !info.isNull() ) { mFormatTypes << (*it); mFormatBox->addItem( info.nameLabel ); } } mInEditMode = false; } void ResourceDirConfig::setEditMode( bool value ) { mFormatBox->setEnabled( !value ); mInEditMode = value; } void ResourceDirConfig::loadSettings( KRES::Resource *res ) { ResourceDir *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } mFormatBox->setCurrentIndex( mFormatTypes.indexOf( resource->format() ) ); mFileNameEdit->setUrl( resource->path() ); if ( mFileNameEdit->url().isEmpty() ) { mFileNameEdit->setUrl( KABC::StdAddressBook::directoryName() ); } } void ResourceDirConfig::saveSettings( KRES::Resource *res ) { ResourceDir *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } if ( mInEditMode ) { resource->setFormat( mFormatTypes[ mFormatBox->currentIndex() ] ); } resource->setPath( mFileNameEdit->url().path() ); } #include "resourcedirconfig.moc" diff --git a/kabc/plugins/file/resourcefileconfig.cpp b/kabc/plugins/file/resourcefileconfig.cpp index 8c78aac46..186e7dca7 100644 --- a/kabc/plugins/file/resourcefileconfig.cpp +++ b/kabc/plugins/file/resourcefileconfig.cpp @@ -1,123 +1,117 @@ /* This file is part of libkabc. Copyright (c) 2002 Tobias Koenig 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 "resourcefileconfig.h" #include "resourcefile.h" #include "kabc/formatfactory.h" #include "kabc/stdaddressbook.h" #include #include #include #include -#include -#include +#include #include using namespace KABC; ResourceFileConfig::ResourceFileConfig( QWidget *parent ) : ConfigWidget( parent ) { - QGridLayout *mainLayout = new QGridLayout( this ); + QFormLayout *mainLayout = new QFormLayout( this ); mainLayout->setMargin( 0 ); - mainLayout->setSpacing( KDialog::spacingHint() ); - QLabel *label = new QLabel( i18n( "Format:" ), this ); mFormatBox = new KComboBox( this ); - mainLayout->addWidget( label, 0, 0 ); - mainLayout->addWidget( mFormatBox, 0, 1 ); + mainLayout->addRow( i18n( "Format:" ), mFormatBox ); - label = new QLabel( i18n( "Location:" ), this ); mFileNameEdit = new KUrlRequester( this ); mFileNameEdit->setMode( KFile::File | KFile::LocalOnly ); + mainLayout->addRow( i18n( "Location:" ), mFileNameEdit ); + connect( mFileNameEdit, SIGNAL( textChanged( const QString & ) ), SLOT( checkFilePermissions( const QString & ) ) ); - mainLayout->addWidget( label, 1, 0 ); - mainLayout->addWidget( mFileNameEdit, 1, 1 ); - FormatFactory *factory = FormatFactory::self(); QStringList formats = factory->formats(); QStringList::Iterator it; for ( it = formats.begin(); it != formats.end(); ++it ) { FormatInfo info = factory->info( *it ); if ( !info.isNull() ) { mFormatTypes << (*it); mFormatBox->addItem( info.nameLabel ); } } mInEditMode = false; } void ResourceFileConfig::setEditMode( bool value ) { mFormatBox->setEnabled( !value ); mInEditMode = value; } void ResourceFileConfig::loadSettings( KRES::Resource *res ) { ResourceFile *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } mFormatBox->setCurrentIndex( mFormatTypes.indexOf( resource->format() ) ); mFileNameEdit->setUrl( KUrl::fromPath( resource->fileName() ) ); if ( mFileNameEdit->url().isEmpty() ) { mFileNameEdit->setUrl( KUrl::fromPath( KABC::StdAddressBook::fileName() ) ); } } void ResourceFileConfig::saveSettings( KRES::Resource *res ) { ResourceFile *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } if ( !mInEditMode ) { resource->setFormat( mFormatTypes[ mFormatBox->currentIndex() ] ); } resource->setFileName( mFileNameEdit->url().path() ); } void ResourceFileConfig::checkFilePermissions( const QString &fileName ) { // If file exist but is not writeable... if ( access( QFile::encodeName( fileName ), F_OK ) == 0 ) { emit setReadOnly( access( QFile::encodeName( fileName ), W_OK ) < 0 ); } } #include "resourcefileconfig.moc" diff --git a/kabc/plugins/ldapkio/resourceldapkioconfig.cpp b/kabc/plugins/ldapkio/resourceldapkioconfig.cpp index f96e5ecb8..2fdb392b4 100644 --- a/kabc/plugins/ldapkio/resourceldapkioconfig.cpp +++ b/kabc/plugins/ldapkio/resourceldapkioconfig.cpp @@ -1,465 +1,464 @@ /* This file is part of libkabc. Copyright (c) 2002 - 2003 Tobias Koenig 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 "resourceldapkioconfig.h" #include "resourceldapkio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "resourceldapkioconfig.moc" using namespace KABC; ResourceLDAPKIOConfig::ResourceLDAPKIOConfig( QWidget *parent ) : KRES::ConfigWidget( parent ) { QBoxLayout *mainLayout = new QVBoxLayout( this ); mainLayout->setMargin( 0 ); - mainLayout->setSpacing( KDialog::spacingHint() ); KPageWidget *pageWidget = new KPageWidget( this ); pageWidget->setFaceType( KPageView::Tabbed ); mCfg = new KLDAP::LdapConfigWidget( KLDAP::LdapConfigWidget::W_USER | KLDAP::LdapConfigWidget::W_PASS | KLDAP::LdapConfigWidget::W_BINDDN | KLDAP::LdapConfigWidget::W_REALM | KLDAP::LdapConfigWidget::W_HOST | KLDAP::LdapConfigWidget::W_PORT | KLDAP::LdapConfigWidget::W_VER | KLDAP::LdapConfigWidget::W_DN | KLDAP::LdapConfigWidget::W_FILTER | KLDAP::LdapConfigWidget::W_TIMELIMIT | KLDAP::LdapConfigWidget::W_SIZELIMIT, this ); mSecurityCfg = new KLDAP::LdapConfigWidget( KLDAP::LdapConfigWidget::W_SECBOX | KLDAP::LdapConfigWidget::W_AUTHBOX, this ); pageWidget->addPage( mCfg, i18nc( "@title:tab general account settings", "General" ) ); pageWidget->addPage( mSecurityCfg, i18nc( "@title:tab account security settings", "Security" ) ); mSubTree = new QCheckBox( i18n( "Sub-tree query" ), this ); KHBox *box = new KHBox( this ); - box->setSpacing( KDialog::spacingHint() ); + box->setSpacing( -1 ); mEditButton = new QPushButton( i18n( "Edit Attributes..." ), box ); mCacheButton = new QPushButton( i18n( "Offline Use..." ), box ); mainLayout->addWidget( pageWidget ); mainLayout->addWidget( mSubTree ); mainLayout->addWidget( box ); connect( mEditButton, SIGNAL( clicked() ), SLOT( editAttributes() ) ); connect( mCacheButton, SIGNAL( clicked() ), SLOT( editCache() ) ); } void ResourceLDAPKIOConfig::loadSettings( KRES::Resource *res ) { ResourceLDAPKIO *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } mCfg->setUser( resource->user() ); mCfg->setPassword( resource->password() ); mCfg->setRealm( resource->realm() ); mCfg->setBindDn( resource->bindDN() ); mCfg->setHost( resource->host() ); mCfg->setPort( resource->port() ); mCfg->setVersion( resource->ver() ); mCfg->setTimeLimit( resource->timeLimit() ); mCfg->setSizeLimit( resource->sizeLimit() ); mCfg->setDn( KLDAP::LdapDN( resource->dn() ) ); mCfg->setFilter( resource->filter() ); mSecurityCfg->setMech( resource->mech() ); if ( resource->isTLS() ) { mSecurityCfg->setSecurity( KLDAP::LdapConfigWidget::TLS ); } else if ( resource->isSSL() ) { mSecurityCfg->setSecurity( KLDAP::LdapConfigWidget::SSL ); } else { mSecurityCfg->setSecurity( KLDAP::LdapConfigWidget::None ); } if ( resource->isAnonymous() ) { mSecurityCfg->setAuth( KLDAP::LdapConfigWidget::Anonymous ); } else if ( resource->isSASL() ) { mSecurityCfg->setAuth( KLDAP::LdapConfigWidget::SASL ); } else { mSecurityCfg->setAuth( KLDAP::LdapConfigWidget::Simple ); } mSubTree->setChecked( resource->isSubTree() ); mAttributes = resource->attributes(); mRDNPrefix = resource->RDNPrefix(); mCachePolicy = resource->cachePolicy(); mCacheDst = resource->cacheDst(); mAutoCache = resource->autoCache(); } void ResourceLDAPKIOConfig::saveSettings( KRES::Resource *res ) { ResourceLDAPKIO *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } resource->setUser( mCfg->user() ); resource->setPassword( mCfg->password() ); resource->setRealm( mCfg->realm() ); resource->setBindDN( mCfg->bindDn() ); resource->setHost( mCfg->host() ); resource->setPort( mCfg->port() ); resource->setVer( mCfg->version() ); resource->setTimeLimit( mCfg->timeLimit() ); resource->setSizeLimit( mCfg->sizeLimit() ); resource->setDn( mCfg->dn().toString() ); resource->setFilter( mCfg->filter() ); resource->setIsAnonymous( mSecurityCfg->auth() == KLDAP::LdapConfigWidget::Anonymous ); resource->setIsSASL( mSecurityCfg->auth() == KLDAP::LdapConfigWidget::SASL ); resource->setMech( mSecurityCfg->mech() ); resource->setIsTLS( mSecurityCfg->security() == KLDAP::LdapConfigWidget::TLS ); resource->setIsSSL( mSecurityCfg->security() == KLDAP::LdapConfigWidget::SSL ); resource->setIsSubTree( mSubTree->isChecked() ); resource->setAttributes( mAttributes ); resource->setRDNPrefix( mRDNPrefix ); resource->setCachePolicy( mCachePolicy ); resource->init(); } void ResourceLDAPKIOConfig::editAttributes() { AttributesDialog dlg( mAttributes, mRDNPrefix, this ); if ( dlg.exec() ) { mAttributes = dlg.attributes(); mRDNPrefix = dlg.rdnprefix(); } } void ResourceLDAPKIOConfig::editCache() { KLDAP::LdapUrl src; QStringList attr; src = mCfg->url(); src.setScope( mSubTree->isChecked() ? KLDAP::LdapUrl::Sub : KLDAP::LdapUrl::One ); if ( !mAttributes.empty() ) { QMap::Iterator it; QStringList attr; for ( it = mAttributes.begin(); it != mAttributes.end(); ++it ) { if ( !it.value().isEmpty() && it.key() != QLatin1String( "objectClass" ) ) { attr.append( it.value() ); } } src.setAttributes( attr ); } src.setExtension( QLatin1String( "x-dir" ), QLatin1String( "base" ) ); OfflineDialog dlg( mAutoCache, mCachePolicy, src, mCacheDst, this ); if ( dlg.exec() ) { mCachePolicy = dlg.cachePolicy(); mAutoCache = dlg.autoCache(); } } AttributesDialog::AttributesDialog( const QMap &attributes, int rdnprefix, QWidget *parent ) : KDialog( parent ) { setCaption( i18n( "Attributes Configuration" ) ); setButtons( Ok | Cancel ); setDefaultButton( Ok ); setModal( true ); showButtonSeparator( true ); mNameDict.insert( QLatin1String( "objectClass" ), i18n( "Object classes" ) ); mNameDict.insert( QLatin1String( "commonName" ), i18n( "Common name" ) ); mNameDict.insert( QLatin1String( "formattedName" ), i18n( "Formatted name" ) ); mNameDict.insert( QLatin1String( "familyName" ), i18n( "Family name" ) ); mNameDict.insert( QLatin1String( "givenName" ), i18n( "Given name" ) ); mNameDict.insert( QLatin1String( "organization" ), i18n( "Organization" ) ); mNameDict.insert( QLatin1String( "title" ), i18nc( "job title", "Title" ) ); mNameDict.insert( QLatin1String( "street" ), i18n( "Street" ) ); mNameDict.insert( QLatin1String( "state" ), i18nc( "state/province", "State" ) ); mNameDict.insert( QLatin1String( "city" ), i18n( "City" ) ); mNameDict.insert( QLatin1String( "postalcode" ), i18n( "Postal code" ) ); mNameDict.insert( QLatin1String( "mail" ), i18nc( "email address", "Email" ) ); mNameDict.insert( QLatin1String( "mailAlias" ), i18n( "Email alias" ) ); mNameDict.insert( QLatin1String( "phoneNumber" ), i18n( "Telephone number" ) ); mNameDict.insert( QLatin1String( "telephoneNumber" ), i18n( "Work telephone number" ) ); mNameDict.insert( QLatin1String( "facsimileTelephoneNumber" ), i18n( "Fax number" ) ); mNameDict.insert( QLatin1String( "mobile" ), i18n( "Cell phone number" ) ); mNameDict.insert( QLatin1String( "pager" ), i18n( "Pager" ) ); mNameDict.insert( QLatin1String( "description" ), i18n( "Note" ) ); mNameDict.insert( QLatin1String( "uid" ), i18n( "UID" ) ); mNameDict.insert( QLatin1String( "jpegPhoto" ), i18n( "Photo" ) ); // default map mDefaultMap.insert( QLatin1String( "objectClass" ), QLatin1String( "inetOrgPerson" ) ); mDefaultMap.insert( QLatin1String( "commonName" ), QLatin1String( "cn" ) ); mDefaultMap.insert( QLatin1String( "formattedName" ), QLatin1String( "displayName" ) ); mDefaultMap.insert( QLatin1String( "familyName" ), QLatin1String( "sn" ) ); mDefaultMap.insert( QLatin1String( "givenName" ), QLatin1String( "givenName" ) ); mDefaultMap.insert( QLatin1String( "title" ), QLatin1String( "title" ) ); mDefaultMap.insert( QLatin1String( "street" ), QLatin1String( "street" ) ); mDefaultMap.insert( QLatin1String( "state" ), QLatin1String( "st" ) ); mDefaultMap.insert( QLatin1String( "city" ), QLatin1String( "l" ) ); mDefaultMap.insert( QLatin1String( "organization" ), QLatin1String( "o" ) ); mDefaultMap.insert( QLatin1String( "postalcode" ), QLatin1String( "postalCode" ) ); mDefaultMap.insert( QLatin1String( "mail" ), QLatin1String( "mail" ) ); mDefaultMap.insert( QLatin1String( "mailAlias" ), QString() ); mDefaultMap.insert( QLatin1String( "phoneNumber" ), QLatin1String( "homePhone" ) ); mDefaultMap.insert( QLatin1String( "telephoneNumber" ), QLatin1String( "telephoneNumber" ) ); mDefaultMap.insert( QLatin1String( "facsimileTelephoneNumber" ), QLatin1String( "facsimileTelephoneNumber" ) ); mDefaultMap.insert( QLatin1String( "mobile" ), QLatin1String( "mobile" ) ); mDefaultMap.insert( QLatin1String( "pager" ), QLatin1String( "pager" ) ); mDefaultMap.insert( QLatin1String( "description" ), QLatin1String( "description" ) ); mDefaultMap.insert( QLatin1String( "uid" ), QLatin1String( "uid" ) ); mDefaultMap.insert( QLatin1String( "jpegPhoto" ), QLatin1String( "jpegPhoto" ) ); // overwrite the default values here QMap kolabMap, netscapeMap, evolutionMap, outlookMap; // kolab kolabMap.insert( QLatin1String( "formattedName" ), QLatin1String( "display-name" ) ); kolabMap.insert( QLatin1String( "mailAlias" ), QLatin1String( "mailalias" ) ); // evolution evolutionMap.insert( QLatin1String( "formattedName" ), QLatin1String( "fileAs" ) ); mMapList.append( attributes ); mMapList.append( kolabMap ); mMapList.append( netscapeMap ); mMapList.append( evolutionMap ); mMapList.append( outlookMap ); QFrame *page = new QFrame( this ); setMainWidget( page ); QGridLayout *layout = new QGridLayout( page ); QLabel *label = new QLabel( i18n( "Template:" ), page ); layout->addWidget( label, 0, 0 ); mMapCombo = new KComboBox( page ); layout->addWidget( mMapCombo, 0, 1 ); mMapCombo->addItem( i18n( "User Defined" ) ); mMapCombo->addItem( i18n( "Kolab" ) ); mMapCombo->addItem( i18n( "Netscape" ) ); mMapCombo->addItem( i18n( "Evolution" ) ); mMapCombo->addItem( i18n( "Outlook" ) ); connect( mMapCombo, SIGNAL( activated( int ) ), SLOT( mapChanged( int ) ) ); label = new QLabel( i18n( "RDN prefix attribute:" ), page ); layout->addWidget( label, 1, 0 ); mRDNCombo = new KComboBox( page ); layout->addWidget( mRDNCombo, 1, 1 ); mRDNCombo->addItem( i18n( "commonName" ) ); mRDNCombo->addItem( i18n( "UID" ) ); mRDNCombo->setCurrentIndex( rdnprefix ); QMap::ConstIterator it; int i, j = 0; for ( i = 2, it = attributes.begin(); it != attributes.end(); ++it, ++i ) { if ( mNameDict[ it.key() ].isEmpty() ) { i--; continue; } if ( ( i - 2 ) == ( mNameDict.count() >> 1 ) ) { i = 0; j = 2; } kDebug() << "itkey:" << it.key() << "i:" << i; label = new QLabel( mNameDict[ it.key() ] + QLatin1Char( ':' ), page ); KLineEdit *lineedit = new KLineEdit( page ); mLineEditDict.insert( it.key(), lineedit ); lineedit->setText( it.value() ); label->setBuddy( lineedit ); layout->addWidget( label, i, j ); layout->addWidget( lineedit, i, j+1 ); } for ( i = 1; i < mMapCombo->count(); ++i ) { QHash::const_iterator it2 = mLineEditDict.constBegin(); while ( it2 != mLineEditDict.constEnd() ) { if ( mMapList[ i ].contains( it2.key() ) ) { if ( mMapList[ i ][ it2.key() ] != it2.value()->text() ) { break; } } else { if ( mDefaultMap[ it2.key() ] != it2.value()->text() ) { break; } } ++it2; } if ( it2 != mLineEditDict.constEnd() ) { mMapCombo->setCurrentIndex( i ); break; } } KAcceleratorManager::manage( this ); } AttributesDialog::~AttributesDialog() { mNameDict.clear(); } QMap AttributesDialog::attributes() const { QMap map; QHash::const_iterator it = mLineEditDict.constBegin(); while ( it != mLineEditDict.constEnd() ) { map.insert( it.key(), it.value()->text() ); ++it; } return map; } int AttributesDialog::rdnprefix() const { return mRDNCombo->currentIndex(); } void AttributesDialog::mapChanged( int pos ) { // apply first the default and than the spezific changes QMap::Iterator it; for ( it = mDefaultMap.begin(); it != mDefaultMap.end(); ++it ) { mLineEditDict[ it.key() ]->setText( it.value() ); } for ( it = mMapList[ pos ].begin(); it != mMapList[ pos ].end(); ++it ) { if ( !it.value().isEmpty() ) { KLineEdit *le = mLineEditDict[ it.key() ]; if ( le ) { le->setText( it.value() ); } } } } OfflineDialog::OfflineDialog( bool autoCache, int cachePolicy, const KUrl &src, const QString &dst, QWidget *parent ) : KDialog( parent ) { setCaption( i18n( "Offline Configuration" ) ); setButtons( Ok | Cancel ); setDefaultButton( Ok ); setModal( true ); showButtonSeparator( true ); QFrame *page = new QFrame( this ); setMainWidget( page ); QVBoxLayout *layout = new QVBoxLayout( page ); mSrc = src; mDst = dst; mCacheBox = new QGroupBox( i18n( "Offline Cache Policy" ), page ); QVBoxLayout *cacheBoxLayout = new QVBoxLayout( mCacheBox ); mCacheGroup = new QButtonGroup( this ); QRadioButton *bt; bt = new QRadioButton( i18n( "Do not use offline cache" ), mCacheBox ); cacheBoxLayout->addWidget( bt ); bt->setDown(true); mCacheGroup->addButton( bt ); bt = new QRadioButton( i18n( "Use local copy if no connection" ), mCacheBox ); cacheBoxLayout->addWidget( bt ); mCacheGroup->addButton( bt ); bt = new QRadioButton( i18n( "Always use local copy" ), mCacheBox ); cacheBoxLayout->addWidget( bt ); mCacheGroup->addButton( bt ); if ( mCacheGroup->button( cachePolicy ) ) { mCacheGroup->button( cachePolicy )->setDown( true ); } mAutoCache = new QCheckBox( i18n( "Refresh offline cache automatically" ), page ); mAutoCache->setChecked( autoCache ); mAutoCache->setEnabled( bt->isChecked() ); connect( bt, SIGNAL(toggled(bool)), mAutoCache, SLOT(setEnabled(bool)) ); QPushButton *lcache = new QPushButton( i18n( "Load into Cache" ), page ); connect( lcache, SIGNAL( clicked() ), SLOT( loadCache() ) ); layout->addWidget( mCacheBox ); layout->addWidget( mAutoCache ); layout->addWidget( lcache ); } OfflineDialog::~OfflineDialog() { } bool OfflineDialog::autoCache() const { return mAutoCache->isChecked(); } int OfflineDialog::cachePolicy() const { return mCacheGroup->checkedId(); } void OfflineDialog::loadCache() { if ( KIO::NetAccess::download( mSrc, mDst, this ) ) { KMessageBox::information( this, i18n( "Successfully downloaded directory server contents." ) ); } else { KMessageBox::error( this, i18n( "An error occurred downloading directory server contents into file %1.", mDst ) ); } } diff --git a/kabc/plugins/net/resourcenetconfig.cpp b/kabc/plugins/net/resourcenetconfig.cpp index 898f6be98..fe0f0f5b7 100644 --- a/kabc/plugins/net/resourcenetconfig.cpp +++ b/kabc/plugins/net/resourcenetconfig.cpp @@ -1,104 +1,98 @@ /* This file is part of libkabc. Copyright (c) 2003 Tobias Koenig 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 "resourcenetconfig.h" #include "resourcenet.h" #include "kabc/formatfactory.h" #include "kabc/stdaddressbook.h" #include #include #include -#include -#include +#include using namespace KABC; ResourceNetConfig::ResourceNetConfig( QWidget *parent ) : ConfigWidget( parent ), mInEditMode( false ) { - QGridLayout *mainLayout = new QGridLayout( this ); + QFormLayout *mainLayout = new QFormLayout( this ); mainLayout->setMargin( 0 ); - mainLayout->setSpacing( KDialog::spacingHint() ); - QLabel *label = new QLabel( i18n( "Format:" ), this ); mFormatBox = new KComboBox( this ); - mainLayout->addWidget( label, 0, 0 ); - mainLayout->addWidget( mFormatBox, 0, 1 ); + mainLayout->addRow( i18n( "Format:" ), mFormatBox ); - label = new QLabel( i18n( "Location:" ), this ); mUrlEdit = new KUrlRequester( this ); mUrlEdit->setMode( KFile::File ); - mainLayout->addWidget( label, 1, 0 ); - mainLayout->addWidget( mUrlEdit, 1, 1 ); + mainLayout->addRow( i18n( "Location:" ), mUrlEdit ); FormatFactory *factory = FormatFactory::self(); QStringList formats = factory->formats(); QStringList::Iterator it; for ( it = formats.begin(); it != formats.end(); ++it ) { FormatInfo info = factory->info( *it ); if ( !info.isNull() ) { mFormatTypes << (*it); mFormatBox->addItem( info.nameLabel ); } } } void ResourceNetConfig::setEditMode( bool value ) { mFormatBox->setEnabled( !value ); mInEditMode = value; } void ResourceNetConfig::loadSettings( KRES::Resource *res ) { ResourceNet *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } mFormatBox->setCurrentIndex( mFormatTypes.indexOf( resource->format() ) ); mUrlEdit->setUrl( resource->url() ); } void ResourceNetConfig::saveSettings( KRES::Resource *res ) { ResourceNet *resource = dynamic_cast( res ); if ( !resource ) { kDebug() << "cast failed"; return; } if ( !mInEditMode ) { resource->setFormat( mFormatTypes[ mFormatBox->currentIndex() ] ); } resource->setUrl( KUrl( mUrlEdit->url() ) ); } #include "resourcenetconfig.moc" diff --git a/kabc/tests/testlock.cpp b/kabc/tests/testlock.cpp index 616954874..3d6a51c02 100644 --- a/kabc/tests/testlock.cpp +++ b/kabc/tests/testlock.cpp @@ -1,210 +1,208 @@ /* This file is part of libkabc. Copyright (c) 2003 Cornelius Schumacher 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 "testlock.h" #include "kabc/stdaddressbook.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KABC; LockWidget::LockWidget( const QString &identifier ) { QVBoxLayout *topLayout = new QVBoxLayout( this ); - topLayout->setMargin( KDialog::marginHint() ); - topLayout->setSpacing( KDialog::spacingHint() ); if ( identifier.isEmpty() ) { mLock = 0; } else { mLock = new Lock( identifier ); int pid = getpid(); QLabel *pidLabel = new QLabel( QLatin1String( "Process ID: " ) + QString::number( pid ), this ); topLayout->addWidget( pidLabel ); QHBoxLayout *identifierLayout = new QHBoxLayout(); identifierLayout->setParent( topLayout ); - topLayout->addItem( identifierLayout ); + topLayout->addLayout( identifierLayout ); QLabel *resourceLabel = new QLabel( QLatin1String( "Identifier:" ), this ); identifierLayout->addWidget( resourceLabel ); QLabel *resourceIdentifier = new QLabel( identifier, this ); identifierLayout->addWidget( resourceIdentifier ); mStatus = new QLabel( QLatin1String( "Status: Unlocked" ), this ); topLayout->addWidget( mStatus ); QPushButton *button = new QPushButton( QLatin1String( "Lock" ), this ); topLayout->addWidget( button ); connect( button, SIGNAL( clicked() ), SLOT( lock() ) ); button = new QPushButton( QLatin1String( "Unlock" ), this ); topLayout->addWidget( button ); connect( button, SIGNAL( clicked() ), SLOT( unlock() ) ); } mLockView = new QTreeWidget( this ); topLayout->addWidget( mLockView ); QStringList headers; headers.append( QLatin1String( "Lock File" ) ); headers.append( QLatin1String( "PID" ) ); headers.append( QLatin1String( "Locking App" ) ); mLockView->setHeaderLabels( headers ); updateLockView(); QPushButton *quitButton = new QPushButton( QLatin1String( "Quit" ), this ); topLayout->addWidget( quitButton ); connect( quitButton, SIGNAL( clicked() ), SLOT( close() ) ); KDirWatch *watch = KDirWatch::self(); connect( watch, SIGNAL( dirty( const QString & ) ), SLOT( updateLockView() ) ); connect( watch, SIGNAL( created( const QString & ) ), SLOT( updateLockView() ) ); connect( watch, SIGNAL( deleted( const QString & ) ), SLOT( updateLockView() ) ); watch->addDir( Lock::locksDir() ); watch->startScan(); } LockWidget::~LockWidget() { delete mLock; } void LockWidget::updateLockView() { mLockView->clear(); QDir dir( Lock::locksDir() ); QStringList files = dir.entryList( QStringList( QLatin1String( "*.lock" ) ) ); QStringList::ConstIterator it; for ( it = files.constBegin(); it != files.constEnd(); ++it ) { if ( *it == QLatin1String( "." ) || *it == QLatin1String( ".." ) ) { continue; } QString app; int pid; if ( !Lock::readLockFile( dir.filePath( *it ), pid, app ) ) { kWarning() << "Unable to open lock file '" << *it << "'"; } else { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText( 0, *it ); item->setText( 1, QString::number( pid ) ); item->setText( 2, app ); mLockView->addTopLevelItem( item ); } } } void LockWidget::lock() { if ( !mLock->lock() ) { KMessageBox::sorry( this, mLock->error() ); } else { mStatus->setText( QLatin1String( "Status: Locked" ) ); } } void LockWidget::unlock() { if ( !mLock->unlock() ) { KMessageBox::sorry( this, mLock->error() ); } else { mStatus->setText( QLatin1String( "Status: Unlocked" ) ); } } int main( int argc, char **argv ) { KAboutData aboutData( "testlock", 0, ki18n( "Test libkabc Lock" ), "0.1" ); KCmdLineArgs::init( argc, argv, &aboutData ); KCmdLineOptions options; options.add( "a" ); options.add( "addressbook", ki18n( "Standard address book" ) ); options.add( "d" ); options.add( "diraddressbook", ki18n( "Standard address book directory resource" ) ); options.add( "+identifier", ki18n( "Identifier of resource to be locked, e.g. filename" ) ); KCmdLineArgs::addCmdLineOptions( options ); KApplication app; QString identifier; KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); if ( args->count() == 1 ) { identifier = args->arg( 0 ); } else if ( args->count() != 0 ) { std::cerr << "Usage: testlock " << std::endl; return 1; } if ( args->isSet( "addressbook" ) ) { if ( args->count() == 1 ) { std::cerr << "Ignoring resource identifier" << std::endl; } identifier = StdAddressBook::fileName(); } if ( args->isSet( "diraddressbook" ) ) { if ( args->count() == 1 ) { std::cerr << "Ignoring resource identifier" << std::endl; } identifier = StdAddressBook::directoryName(); } LockWidget mainWidget( identifier ); mainWidget.show(); return app.exec(); } #include "testlock.moc" diff --git a/kcal/confirmsavedialog.cpp b/kcal/confirmsavedialog.cpp index 2013c65d4..d587151bd 100644 --- a/kcal/confirmsavedialog.cpp +++ b/kcal/confirmsavedialog.cpp @@ -1,96 +1,95 @@ /* This file is part of the kcal library. Copyright (c) 2004 Cornelius Schumacher 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 "confirmsavedialog.h" #include #include #include #include #include using namespace KCal; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KCal::ConfirmSaveDialog::Private { public: Private() {} QTreeWidget *mListView; }; //@endcond ConfirmSaveDialog::ConfirmSaveDialog( const QString &destination, QWidget *parent ) : KDialog( parent ), d( new KCal::ConfirmSaveDialog::Private ) { setCaption( i18n( "Confirm Save" ) ); setModal( true ); setButtons( Ok | Cancel ); setDefaultButton( Ok ); QFrame *topFrame = new QFrame( this ); setMainWidget( topFrame ); QBoxLayout *topLayout = new QVBoxLayout( topFrame ); - topLayout->setSpacing( spacingHint() ); QLabel *label = new QLabel( i18n( "You have requested to save the following objects to '%1':", destination ), topFrame ); topLayout->addWidget( label ); QStringList headers; headers << i18n( "Operation" ) << i18n( "Type" ) << i18n( "Summary" ) << i18n( "UID" ); d->mListView = new QTreeWidget( topFrame ); d->mListView->setColumnCount( 4 ); d->mListView->setHeaderLabels( headers ); topLayout->addWidget( d->mListView ); } ConfirmSaveDialog::~ConfirmSaveDialog() { delete d; } void ConfirmSaveDialog::addIncidences( const Incidence::List &incidences, const QString &operation ) { Incidence::List::ConstIterator it; for ( it = incidences.begin(); it != incidences.end(); ++it ) { Incidence *i = *it; QTreeWidgetItem *item = new QTreeWidgetItem( d->mListView ); item->setText( 0, operation ); item->setText( 1, i->type() ); item->setText( 2, i->summary() ); item->setText( 3, i->uid() ); } } diff --git a/kholidays/holidays/CMakeLists.txt b/kholidays/holidays/CMakeLists.txt index 42c5eceda..9455553c3 100644 --- a/kholidays/holidays/CMakeLists.txt +++ b/kholidays/holidays/CMakeLists.txt @@ -1,57 +1,58 @@ install( FILES holiday_ar holiday_at holiday_au holiday_bavarian holiday_BelgiumDutch holiday_BelgiumFrench holiday_BelgiumWalloon holiday_bg holiday_br holiday_ca holiday_catalan holiday_ch holiday_cl + holiday_cn holiday_co holiday_cz holiday_de holiday_dk holiday_ee holiday_es holiday_fi holiday_fr holiday_frswiss holiday_gb holiday_gr holiday_gt holiday_hu holiday_ie holiday_il holiday_in holiday_is holiday_it holiday_jm holiday_jp holiday_lt holiday_mx holiday_nl holiday_no holiday_nz holiday_pl holiday_pt holiday_py holiday_quebec holiday_ro holiday_ru holiday_se holiday_si holiday_sk holiday_Suedtirol holiday_th holiday_ua holiday_us holiday_uy holiday_za DESTINATION ${DATA_INSTALL_DIR}/libkholidays) diff --git a/kholidays/holidays/holiday_cn b/kholidays/holidays/holiday_cn new file mode 100644 index 000000000..7f9e8940c --- /dev/null +++ b/kholidays/holidays/holiday_cn @@ -0,0 +1,40 @@ +: This file is UTF-8 encoded +: Holiday file for P.R. China +: Data from http://en.wikipedia.org/w/index.php?title=Public_holidays_in_the_People%27s_Republic_of_China&oldid=297123230 +: Author: Patrick Nagel +: +"元旦 (New Year)" weekend on 1 january +magenta "国际妇女节 (International Women's Day)" on 8 march +black "植树节 (Arbor Day)" on 12 march +"劳动节 (Labor Day)" weekend on 1 may +blue "青年节 (Youth Day)" on 4 may +cyan "六一儿童节 (Children's Day)" on 1 june +black "建党节 (CPC Founding Day)" on 1 july +green "建军节 (Army Day)" on 1 august +"国庆节 (National Day)" weekend on 1 october length 3 days +: +: Lunar calendar based holidays +: and special free (on weekdays) / work (on weekends) arrangements for 2009 +: yellow for free days, red for working days +: Data from http://cnreviews.com/life/living-in-china/china_public_holiday_2009_20081210.html +: and various en.wikipedia.org pages +: +yellow "节假日 (day off)" weekend on 2 january 2009 +red "工作日 (working day)" on 4 january 2009 +red "工作日 (working day)" on 24 january 2009 +"春节 (Chinese New Year / Spring Festival)" weekend on 25 january 2009 length 3 days +yellow "节假日 (day off)" weekend on 28 january 2009 length 3 days +red "工作日 (working day)" on 1 february 2009 +black "元宵节 (Lantern Festival)" on 9 february 2009 +"清明节 (Qing Ming Festival / Tomb Sweeping Day)" weekend on 4 april 2009 +yellow "节假日 (day off)" weekend on 6 april 2009 +"端午节 (Dragon Boat Festival)" weekend on 28 may 2009 +yellow "节假日 (day off)" weekend on 29 may 2009 +red "工作日 (working day)" on 31 may 2009 +black "七夕节 Lover's Day" on 26 august 2009 +: black "中元节 Ghost Festival" on 3 september 2009 +red "工作日 (working day)" on 27 september 2009 +"中秋节 (Mid-Autumn Festival)" weekend on 3 october 2009 +yellow "节假日 (day off)" weekend on 5 october 2009 length 4 days +red "工作日 (working day)" on 10 october 2009 +black "重阳节 (Double Ninth Day / Elders' Day)" on 26 october 2009 diff --git a/kioslave/imap4/imap4.cpp b/kioslave/imap4/imap4.cpp index ca0e42e9a..bbe67fe31 100644 --- a/kioslave/imap4/imap4.cpp +++ b/kioslave/imap4/imap4.cpp @@ -1,2653 +1,2653 @@ /********************************************************************** * * imap4.cc - IMAP4rev1 KIOSlave * Copyright (C) 2001-2002 Michael Haeckel * Copyright (C) 1999 John Corey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Send comments and bug fixes to jcorey@fruity.ath.cx * *********************************************************************/ /** * @class IMAP4Protocol * @note References: * - RFC 2060 - Internet Message Access Protocol - Version 4rev1 - December 1996 * - RFC 2192 - IMAP URL Scheme - September 1997 * - RFC 1731 - IMAP Authentication Mechanisms - December 1994 * (Discusses KERBEROSv4, GSSAPI, and S/Key) * - RFC 2195 - IMAP/POP AUTHorize Extension for Simple Challenge/Response * - September 1997 (CRAM-MD5 authentication method) * - RFC 2104 - HMAC: Keyed-Hashing for Message Authentication - February 1997 * - RFC 2086 - IMAP4 ACL extension - January 1997 * - http://www.ietf.org/internet-drafts/draft-daboo-imap-annotatemore-05.txt * IMAP ANNOTATEMORE draft - April 2004. * * * Supported URLs: * \verbatim imap://server/ imap://user:pass@server/ imap://user;AUTH=method:pass@server/ imap://server/folder/ * \endverbatim * These URLs cause the following actions (in order): * - Prompt for user/pass, list all folders in home directory * - Uses LOGIN to log in * - Uses AUTHENTICATE to log in * - List messages in folder * * @note API notes: * Not receiving the required write access for a folder means * ERR_CANNOT_OPEN_FOR_WRITING. * ERR_DOES_NOT_EXIST is reserved for folders. */ #include "imap4.h" #include #include #include #include #include #include #include #include #include extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "kdemacros.h" #define IMAP_PROTOCOL "imap" #define IMAP_SSL_PROTOCOL "imaps" const int ImapPort = 143; const int ImapsPort = 993; using namespace KIO; extern "C" { void sigalrm_handler (int); KDE_EXPORT int kdemain (int argc, char **argv); } int kdemain (int argc, char **argv) { kDebug(7116) <<"IMAP4::kdemain"; KComponentData instance ("kio_imap4"); if (argc != 4) { fprintf(stderr, "Usage: kio_imap4 protocol domain-socket1 domain-socket2\n"); ::exit (-1); } if (!initSASL()) ::exit(-1); //set debug handler IMAP4Protocol *slave; if (strcasecmp (argv[1], IMAP_SSL_PROTOCOL) == 0) slave = new IMAP4Protocol (argv[2], argv[3], true); else if (strcasecmp (argv[1], IMAP_PROTOCOL) == 0) slave = new IMAP4Protocol (argv[2], argv[3], false); else abort (); slave->dispatchLoop (); delete slave; sasl_done(); return 0; } void sigchld_handler (int signo) { // A signal handler that calls for example waitpid has to save errno // before and restore it afterwards. // (cf. https://www.securecoding.cert.org/confluence/display/cplusplus/ERR32-CPP.+Do+not+rely+on+indeterminate+values+of+errno) const int save_errno = errno; int pid, status; while (signo == SIGCHLD) { pid = waitpid (-1, &status, WNOHANG); if (pid <= 0) { // Reinstall signal handler, since Linux resets to default after // the signal occurred ( BSD handles it different, but it should do // no harm ). KDE_signal (SIGCHLD, sigchld_handler); break; } } errno = save_errno; } IMAP4Protocol::IMAP4Protocol (const QByteArray & pool, const QByteArray & app, bool isSSL) :TCPSlaveBase ((isSSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL), pool, app, isSSL), imapParser (), mimeIO (), mySSL( isSSL ), relayEnabled( false ), cacheOutput( false ), decodeContent( false ), outputBuffer(&outputCache), outputBufferIndex(0), mProcessedSize( 0 ), readBufferLen( 0 ), mTimeOfLastNoop( QDateTime() ) { readBuffer[0] = 0x00; } IMAP4Protocol::~IMAP4Protocol () { disconnectFromHost(); kDebug(7116) <<"IMAP4: Finishing"; } void IMAP4Protocol::get (const KUrl & _url) { if (!makeLogin()) return; kDebug(7116) <<"IMAP4::get -" << _url.prettyUrl(); QString aBox, aSequence, aType, aSection, aValidity, aDelimiter, aInfo; enum IMAP_TYPE aEnum = parseURL (_url, aBox, aSection, aType, aSequence, aValidity, aDelimiter, aInfo); if (aEnum != ITYPE_ATTACH) mimeType (getMimeType(aEnum)); if (aInfo == "DECODE") decodeContent = true; if (aSequence == "0:0" && getState() == ISTATE_SELECT) { CommandPtr cmd = doCommand (imapCommand::clientNoop()); completeQueue.removeAll(cmd); } if (aSequence.isEmpty ()) { aSequence = "1:*"; } mProcessedSize = 0; CommandPtr cmd; if (!assureBox (aBox, true)) return; #ifdef USE_VALIDITY if (selectInfo.uidValidityAvailable () && !aValidity.isEmpty () && selectInfo.uidValidity () != aValidity.toULong ()) { // this url is stale error (ERR_COULD_NOT_READ, _url.prettyUrl()); return; } else #endif { // The "section" specified by the application can be: // * empty (which means body, size and flags) // * a known keyword, like STRUCTURE, ENVELOPE, HEADER, BODY.PEEK[...] // (in which case the slave has some logic to add the necessary items) // * Otherwise, it specifies the exact data items to request. In this case, all // the logic is in the app. QString aUpper = aSection.toUpper(); if (aUpper.contains("STRUCTURE")) { aSection = "BODYSTRUCTURE"; } else if (aUpper.contains("ENVELOPE")) { aSection = "UID RFC822.SIZE FLAGS ENVELOPE"; if (hasCapability("IMAP4rev1")) { aSection += " BODY.PEEK[HEADER.FIELDS (REFERENCES)]"; } else { // imap4 does not know HEADER.FIELDS aSection += " RFC822.HEADER.LINES (REFERENCES)"; } } else if (aUpper == "HEADER") { aSection = "UID RFC822.HEADER RFC822.SIZE FLAGS"; } else if (aUpper.contains("BODY.PEEK[")) { if (aUpper.contains("BODY.PEEK[]")) { if (!hasCapability("IMAP4rev1")) // imap4 does not know BODY.PEEK[] aSection.replace("BODY.PEEK[]", "RFC822.PEEK"); } aSection.prepend("UID RFC822.SIZE FLAGS "); } else if (aSection.isEmpty()) { aSection = "UID BODY[] RFC822.SIZE FLAGS"; } if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX) { // write the digest header cacheOutput = true; outputLine ("Content-Type: multipart/digest; boundary=\"IMAPDIGEST\"\r\n", 55); if (selectInfo.recentAvailable ()) outputLineStr ("X-Recent: " + QString::number(selectInfo.recent ()) + "\r\n"); if (selectInfo.countAvailable ()) outputLineStr ("X-Count: " + QString::number(selectInfo.count ()) + "\r\n"); if (selectInfo.unseenAvailable ()) outputLineStr ("X-Unseen: " + QString::number(selectInfo.unseen ()) + "\r\n"); if (selectInfo.uidValidityAvailable ()) outputLineStr ("X-uidValidity: " + QString::number(selectInfo.uidValidity ()) + "\r\n"); if (selectInfo.uidNextAvailable ()) outputLineStr ("X-UidNext: " + QString::number(selectInfo.uidNext ()) + "\r\n"); if (selectInfo.flagsAvailable ()) outputLineStr ("X-Flags: " + QString::number(selectInfo.flags ()) + "\r\n"); if (selectInfo.permanentFlagsAvailable ()) outputLineStr ("X-PermanentFlags: " + QString::number(selectInfo.permanentFlags ()) + "\r\n"); if (selectInfo.readWriteAvailable ()) { if (selectInfo.readWrite()) { outputLine ("X-Access: Read/Write\r\n", 22); } else { outputLine ("X-Access: Read only\r\n", 21); } } outputLine ("\r\n", 2); flushOutput(QString()); cacheOutput = false; } if (aEnum == ITYPE_MSG || (aEnum == ITYPE_ATTACH && !decodeContent)) relayEnabled = true; // normal mode, relay data if (aSequence != "0:0") { QString contentEncoding; if (aEnum == ITYPE_ATTACH && decodeContent) { // get the MIME header and fill getLastHandled() QString mySection = aSection; mySection.replace(']', ".MIME]"); cmd = sendCommand (imapCommand::clientFetch (aSequence, mySection)); do { while (!parseLoop ()) {} } while (!cmd->isComplete ()); completeQueue.removeAll (cmd); // get the content encoding now because getLastHandled will be cleared if (getLastHandled() && getLastHandled()->getHeader()) contentEncoding = getLastHandled()->getHeader()->getEncoding(); // from here on collect the data // it is send to the client in flushOutput in one go // needed to decode the content cacheOutput = true; } cmd = sendCommand (imapCommand::clientFetch (aSequence, aSection)); int res; aUpper = aSection.toUpper(); do { while (!(res = parseLoop())) {} if (res == -1) break; mailHeader *lastone = 0; imapCache *cache = getLastHandled (); if (cache) lastone = cache->getHeader (); if (cmd && !cmd->isComplete ()) { if ( aUpper.contains("BODYSTRUCTURE") || aUpper.contains("FLAGS") || aUpper.contains("UID") || aUpper.contains("ENVELOPE") || (aUpper.contains("BODY.PEEK[0]") && (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX))) { if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX) { // write the mime header (default is here message/rfc822) outputLine ("--IMAPDIGEST\r\n", 14); cacheOutput = true; if (cache->getUid () != 0) outputLineStr ("X-UID: " + QString::number(cache->getUid ()) + "\r\n"); if (cache->getSize () != 0) outputLineStr ("X-Length: " + QString::number(cache->getSize ()) + "\r\n"); if (!cache->getDate ().isEmpty()) outputLineStr ("X-Date: " + cache->getDate () + "\r\n"); if (cache->getFlags () != 0) outputLineStr ("X-Flags: " + QString::number(cache->getFlags ()) + "\r\n"); } else cacheOutput = true; if ( lastone && !decodeContent ) lastone->outputPart (*this); cacheOutput = false; flushOutput(contentEncoding); } } // if not complete } while (cmd && !cmd->isComplete ()); if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX) { // write the end boundary outputLine ("--IMAPDIGEST--\r\n", 16); } completeQueue.removeAll (cmd); } } // just to keep everybody happy when no data arrived data (QByteArray ()); finished (); relayEnabled = false; cacheOutput = false; kDebug(7116) <<"IMAP4::get - finished"; } void IMAP4Protocol::listDir (const KUrl & _url) { kDebug(7116) <<" IMAP4::listDir -" << _url.prettyUrl(); if (_url.path().isEmpty()) { KUrl url = _url; url.setPath("/"); redirection( url ); finished(); return; } QString myBox, mySequence, myLType, mySection, myValidity, myDelimiter, myInfo; // parseURL with caching enum IMAP_TYPE myType = parseURL (_url, myBox, mySection, myLType, mySequence, myValidity, myDelimiter, myInfo, true); if (!makeLogin()) return; if (myType == ITYPE_DIR || myType == ITYPE_DIR_AND_BOX) { QString listStr = myBox; CommandPtr cmd; if (!listStr.isEmpty () && !listStr.endsWith(myDelimiter) && mySection != "FOLDERONLY") listStr += myDelimiter; if (mySection.isEmpty()) { listStr += '%'; } else if (mySection == "COMPLETE") { listStr += '*'; } kDebug(7116) <<"IMAP4Protocol::listDir - listStr=" << listStr; cmd = doCommand (imapCommand::clientList ("", listStr, (myLType == "LSUB" || myLType == "LSUBNOCHECK"))); if (cmd->result () == "OK") { QString mailboxName; UDSEntry entry; KUrl aURL = _url; if ( aURL.path().contains(';') ) aURL.setPath(aURL.path().left(aURL.path().indexOf(';'))); kDebug(7116) <<"IMAP4Protocol::listDir - got" << listResponses.count (); if (myLType == "LSUB") { // fire the same command as LIST to check if the box really exists QList listResponsesSave = listResponses; doCommand (imapCommand::clientList ("", listStr, false)); for (QList< imapList >::Iterator it = listResponsesSave.begin (); it != listResponsesSave.end (); ++it) { bool boxOk = false; for (QList< imapList >::Iterator it2 = listResponses.begin (); it2 != listResponses.end (); ++it2) { if ((*it2).name() == (*it).name()) { boxOk = true; // copy the flags from the LIST-command (*it) = (*it2); break; } } if (boxOk) doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY")); else // this folder is dead kDebug(7116) <<"IMAP4Protocol::listDir - suppress" << (*it).name(); } listResponses = listResponsesSave; } else // LIST or LSUBNOCHECK { for (QList< imapList >::Iterator it = listResponses.begin (); it != listResponses.end (); ++it) { doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY")); } } entry.clear (); listEntry (entry, true); } else { error (ERR_CANNOT_ENTER_DIRECTORY, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } if ((myType == ITYPE_BOX || myType == ITYPE_DIR_AND_BOX) && myLType != "LIST" && myLType != "LSUB" && myLType != "LSUBNOCHECK") { KUrl aURL = _url; aURL.setQuery (QString()); const QString encodedUrl = aURL.url(KUrl::LeaveTrailingSlash); // utf-8 if (!_url.query ().isEmpty ()) { QString query = KUrl::fromPercentEncoding (_url.query().toLatin1()); query = query.right (query.length () - 1); if (!query.isEmpty()) { CommandPtr cmd; if (!assureBox (myBox, true)) return; if (!selectInfo.countAvailable() || selectInfo.count()) { cmd = doCommand (imapCommand::clientSearch (query)); if (cmd->result() != "OK") { error(ERR_UNSUPPORTED_ACTION, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); QStringList list = getResults (); int stretch = 0; if (selectInfo.uidNextAvailable ()) stretch = QString::number(selectInfo.uidNext ()).length (); UDSEntry entry; imapCache fake; for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { fake.setUid((*it).toULong()); doListEntry (encodedUrl, stretch, &fake); } entry.clear (); listEntry (entry, true); } } } else { if (!assureBox (myBox, true)) return; kDebug(7116) <<"IMAP4: select returned:"; if (selectInfo.recentAvailable ()) kDebug(7116) <<"Recent:" << selectInfo.recent () <<"d"; if (selectInfo.countAvailable ()) kDebug(7116) <<"Count:" << selectInfo.count () <<"d"; if (selectInfo.unseenAvailable ()) kDebug(7116) <<"Unseen:" << selectInfo.unseen () <<"d"; if (selectInfo.uidValidityAvailable ()) kDebug(7116) <<"uidValidity:" << selectInfo.uidValidity () <<"d"; if (selectInfo.flagsAvailable ()) kDebug(7116) <<"Flags:" << selectInfo.flags () <<"d"; if (selectInfo.permanentFlagsAvailable ()) kDebug(7116) <<"PermanentFlags:" << selectInfo.permanentFlags () <<"d"; if (selectInfo.readWriteAvailable ()) kDebug(7116) <<"Access:" << (selectInfo.readWrite ()?"Read/Write" :"Read only"); #ifdef USE_VALIDITY if (selectInfo.uidValidityAvailable () && selectInfo.uidValidity () != myValidity.toULong ()) { //redirect KUrl newUrl = _url; newUrl.setPath ('/' + myBox + ";UIDVALIDITY=" + QString::number(selectInfo.uidValidity ())); kDebug(7116) <<"IMAP4::listDir - redirecting to" << newUrl.prettyUrl(); redirection (newUrl); } else #endif if (selectInfo.count () > 0) { int stretch = 0; if (selectInfo.uidNextAvailable ()) stretch = QString::number(selectInfo.uidNext ()).length (); // kDebug(7116) << selectInfo.uidNext() <<"d used to stretch" << stretch; UDSEntry entry; if (mySequence.isEmpty()) mySequence = "1:*"; bool withSubject = mySection.isEmpty(); if (mySection.isEmpty()) mySection = "UID RFC822.SIZE ENVELOPE"; bool withFlags = mySection.toUpper().contains("FLAGS") ; CommandPtr fetch = sendCommand (imapCommand:: clientFetch (mySequence, mySection)); imapCache *cache; do { while (!parseLoop ()) {} cache = getLastHandled (); if (cache && !fetch->isComplete()) doListEntry (encodedUrl, stretch, cache, withFlags, withSubject); } while (!fetch->isComplete ()); entry.clear (); listEntry (entry, true); } } } if ( !selectInfo.alert().isNull() ) { if ( !myBox.isEmpty() ) { warning( i18n( "Message from %1 while processing '%2': %3", myHost, myBox, selectInfo.alert() ) ); } else { warning( i18n( "Message from %1: %2", myHost, selectInfo.alert() ) ); } selectInfo.setAlert( 0 ); } kDebug(7116) <<"IMAP4Protocol::listDir - Finishing listDir"; finished (); } void IMAP4Protocol::setHost (const QString & _host, quint16 _port, const QString & _user, const QString & _pass) { if (myHost != _host || myPort != _port || myUser != _user || myPass != _pass) { // what's the point of doing 4 string compares to avoid 4 string copies? // DF: I guess to avoid calling closeConnection() unnecessarily. if (!myHost.isEmpty ()) closeConnection (); myHost = _host; if (_port == 0) myPort = (mySSL) ? ImapsPort : ImapPort; else myPort = _port; myUser = _user; myPass = _pass; } } void IMAP4Protocol::parseRelay (const QByteArray & buffer) { if (relayEnabled) { // relay data immediately data( buffer ); mProcessedSize += buffer.size(); processedSize( mProcessedSize ); } else if (cacheOutput) { // collect data if ( !outputBuffer.isOpen() ) { outputBuffer.open(QIODevice::WriteOnly); } outputBuffer.seek( outputBufferIndex ); outputBuffer.write(buffer, buffer.size()); outputBufferIndex += buffer.size(); } } void IMAP4Protocol::parseRelay (ulong len) { if (relayEnabled) totalSize (len); } bool IMAP4Protocol::parseRead(QByteArray & buffer, long len, long relay) { const long int bufLen = 8192; char buf[bufLen]; // FIXME while (buffer.size() < len ) { ssize_t readLen = myRead(buf, qMin(len - buffer.size(), bufLen - 1)); if (readLen == 0) { kDebug(7116) <<"parseRead: readLen == 0 - connection broken"; error (ERR_CONNECTION_BROKEN, myHost); setState(ISTATE_CONNECT); closeConnection(); return false; } if (relay > buffer.size()) { QByteArray relayData; ssize_t relbuf = relay - buffer.size(); int currentRelay = qMin(relbuf, readLen); relayData = QByteArray::fromRawData(buf, currentRelay); parseRelay(relayData); relayData.clear(); } { QBuffer stream( &buffer ); stream.open (QIODevice::WriteOnly); stream.seek (buffer.size ()); stream.write (buf, readLen); stream.close (); } } return (buffer.size() == len); } bool IMAP4Protocol::parseReadLine (QByteArray & buffer, long relay) { if (myHost.isEmpty()) return false; while (true) { ssize_t copyLen = 0; if (readBufferLen > 0) { while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') copyLen++; if (copyLen < readBufferLen) copyLen++; if (relay > 0) { QByteArray relayData; if (copyLen < (ssize_t) relay) relay = copyLen; relayData = QByteArray::fromRawData (readBuffer, relay); parseRelay (relayData); relayData.clear(); // kDebug(7116) <<"relayed :" << relay <<"d"; } // append to buffer { int oldsize = buffer.size(); buffer.resize(oldsize + copyLen); memcpy(buffer.data() + oldsize, readBuffer, copyLen); // kDebug(7116) <<"appended" << copyLen <<"d got now" << buffer.size(); } readBufferLen -= copyLen; if (readBufferLen) memmove(readBuffer, &readBuffer[copyLen], readBufferLen); if (buffer[buffer.size() - 1] == '\n') return true; } if (!isConnected()) { kDebug(7116) <<"parseReadLine - connection broken"; error (ERR_CONNECTION_BROKEN, myHost); setState(ISTATE_CONNECT); closeConnection(); return false; } if (!waitForResponse( responseTimeout() )) { error(ERR_SERVER_TIMEOUT, myHost); setState(ISTATE_CONNECT); closeConnection(); return false; } readBufferLen = read(readBuffer, IMAP_BUFFER - 1); if (readBufferLen == 0) { kDebug(7116) <<"parseReadLine: readBufferLen == 0 - connection broken"; error (ERR_CONNECTION_BROKEN, myHost); setState(ISTATE_CONNECT); closeConnection(); return false; } } } void IMAP4Protocol::setSubURL (const KUrl & _url) { kDebug(7116) <<"IMAP4::setSubURL -" << _url.prettyUrl(); KIO::TCPSlaveBase::setSubUrl (_url); } void IMAP4Protocol::put (const KUrl & _url, int, KIO::JobFlags) { kDebug(7116) <<"IMAP4::put -" << _url.prettyUrl(); // KIO::TCPSlaveBase::put(_url,permissions,flags) QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; enum IMAP_TYPE aType = parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); // see if it is a box if (aType != ITYPE_BOX && aType != ITYPE_DIR_AND_BOX) { if (aBox[aBox.length () - 1] == '/') aBox = aBox.right (aBox.length () - 1); CommandPtr cmd = doCommand (imapCommand::clientCreate (aBox)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_WRITE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } else { QList < QByteArray* > bufferList; int length = 0; int result; // Loop until we got 'dataEnd' do { QByteArray *buffer = new QByteArray (); dataReq (); // Request for data result = readData (*buffer); if (result > 0) { bufferList.append (buffer); length += result; } else { delete buffer; } } while (result > 0); if (result != 0) { error (ERR_ABORTED, _url.prettyUrl()); return; } CommandPtr cmd = sendCommand (imapCommand::clientAppend (aBox, aSection, length)); while (!parseLoop ()) {} // see if server is waiting if (!cmd->isComplete () && !getContinuation ().isEmpty ()) { bool sendOk = true; ulong wrote = 0; QByteArray *buffer; QListIterator it(bufferList); // send data to server while (it.hasNext() && sendOk) { buffer = it.next(); sendOk = (write (buffer->data (), buffer->size ()) == (ssize_t) buffer->size ()); wrote += buffer->size (); processedSize(wrote); delete buffer; if (!sendOk) { error (ERR_CONNECTION_BROKEN, myHost); completeQueue.removeAll (cmd); setState(ISTATE_CONNECT); closeConnection(); return; } } parseWriteLine (""); // Wait until cmd is complete, or connection breaks. while (!cmd->isComplete () && getState() != ISTATE_NO) parseLoop (); if ( getState() == ISTATE_NO ) { // TODO KDE4: pass cmd->resultInfo() as third argument. // ERR_CONNECTION_BROKEN expects a host, no way to pass details about the problem. error( ERR_CONNECTION_BROKEN, myHost ); completeQueue.removeAll (cmd); closeConnection(); return; } else if (cmd->result () != "OK") { error( ERR_SLAVE_DEFINED, cmd->resultInfo() ); completeQueue.removeAll (cmd); return; } else { if (hasCapability("UIDPLUS")) { QString uid = cmd->resultInfo(); if ( uid.contains("APPENDUID") ) { - uid = uid.section(" ", 2, 2); + uid = uid.section(' ', 2, 2); uid.truncate(uid.length()-1); infoMessage("UID "+uid); } } // MUST reselect to get the new message else if (aBox == getCurrentBox ()) { cmd = doCommand (imapCommand:: clientSelect (aBox, !selectInfo.readWrite ())); completeQueue.removeAll (cmd); } } } else { //error (ERR_COULD_NOT_WRITE, myHost); // Better ship the error message, e.g. "Over Quota" error (ERR_SLAVE_DEFINED, cmd->resultInfo()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } finished (); } void IMAP4Protocol::mkdir (const KUrl & _url, int) { kDebug(7116) <<"IMAP4::mkdir -" << _url.prettyUrl(); QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); kDebug(7116) <<"IMAP4::mkdir - create" << aBox; CommandPtr cmd = doCommand (imapCommand::clientCreate(aBox)); if (cmd->result () != "OK") { kDebug(7116) <<"IMAP4::mkdir -" << cmd->resultInfo(); error (ERR_COULD_NOT_MKDIR, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); // start a new listing to find the type of the folder enum IMAP_TYPE type = parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); if (type == ITYPE_BOX) { bool ask = ( aInfo.contains( "ASKUSER" ) ); if ( ask && messageBox(QuestionYesNo, i18n("The following folder will be created on the server: %1 " "What do you want to store in this folder?", aBox ), i18n("Create Folder"), i18n("&Messages"), i18n("&Subfolders")) == KMessageBox::No ) { cmd = doCommand(imapCommand::clientDelete(aBox)); completeQueue.removeAll (cmd); cmd = doCommand(imapCommand::clientCreate(aBox + aDelimiter)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_MKDIR, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } } cmd = doCommand(imapCommand::clientSubscribe(aBox)); completeQueue.removeAll(cmd); finished (); } void IMAP4Protocol::copy (const KUrl & src, const KUrl & dest, int, KIO::JobFlags flags) { kDebug(7116) <<"IMAP4::copy - [" << ((flags & KIO::Overwrite) ?"Overwrite" :"NoOverwrite") <<"]" << src.prettyUrl() <<" ->" << dest.prettyUrl(); QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo; QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo; enum IMAP_TYPE sType = parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo); enum IMAP_TYPE dType = parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo); // see if we have to create anything if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX) { // this might be konqueror int sub = dBox.indexOf (sBox); // might be moving to upper folder if (sub > 0) { KUrl testDir = dest; QString subDir = dBox.right (dBox.length () - dBox.lastIndexOf ('/')); QString topDir = dBox.left (sub); testDir.setPath ('/' + topDir); dType = parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo); kDebug(7116) <<"IMAP4::copy - checking this destination" << topDir; // see if this is what the user wants if (dType == ITYPE_BOX || dType == ITYPE_DIR_AND_BOX) { kDebug(7116) <<"IMAP4::copy - assuming this destination" << topDir; dBox = topDir; } else { // maybe if we create a new mailbox topDir = '/' + topDir + subDir; testDir.setPath (topDir); kDebug(7116) <<"IMAP4::copy - checking this destination" << topDir; dType = parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo); if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX) { // ok then we'll create a mailbox CommandPtr cmd = doCommand (imapCommand::clientCreate (topDir)); // on success we'll use it, else we'll just try to create the given dir if (cmd->result () == "OK") { kDebug(7116) <<"IMAP4::copy - assuming this destination" << topDir; dType = ITYPE_BOX; dBox = topDir; } else { completeQueue.removeAll (cmd); cmd = doCommand (imapCommand::clientCreate (dBox)); if (cmd->result () == "OK") dType = ITYPE_BOX; else error (ERR_COULD_NOT_WRITE, dest.prettyUrl()); } completeQueue.removeAll (cmd); } } } } if (sType == ITYPE_MSG || sType == ITYPE_BOX || sType == ITYPE_DIR_AND_BOX) { //select the source box if (!assureBox(sBox, true)) return; kDebug(7116) <<"IMAP4::copy -" << sBox <<" ->" << dBox; //issue copy command CommandPtr cmd = doCommand (imapCommand::clientCopy (dBox, sSequence)); if (cmd->result () != "OK") { kError(5006) <<"IMAP4::copy -" << cmd->resultInfo(); error (ERR_COULD_NOT_WRITE, dest.prettyUrl()); completeQueue.removeAll (cmd); return; } else { if (hasCapability("UIDPLUS")) { QString uid = cmd->resultInfo(); if ( uid.contains("COPYUID") ) { - uid = uid.section(" ", 2, 3); + uid = uid.section(' ', 2, 3); uid.truncate(uid.length()-1); infoMessage("UID "+uid); } } } completeQueue.removeAll (cmd); } else { error (ERR_ACCESS_DENIED, src.prettyUrl()); return; } finished (); } void IMAP4Protocol::del (const KUrl & _url, bool isFile) { kDebug(7116) <<"IMAP4::del - [" << (isFile ?"File" :"NoFile") <<"]" << _url.prettyUrl(); QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; enum IMAP_TYPE aType = parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); switch (aType) { case ITYPE_BOX: case ITYPE_DIR_AND_BOX: if (!aSequence.isEmpty ()) { if (aSequence == "*") { if (!assureBox (aBox, false)) return; CommandPtr cmd = doCommand (imapCommand::clientExpunge ()); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } else { // if open for read/write if (!assureBox (aBox, false)) return; CommandPtr cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } } else { if (getCurrentBox() == aBox) { CommandPtr cmd = doCommand(imapCommand::clientClose()); completeQueue.removeAll(cmd); setState(ISTATE_LOGIN); } // We unsubscribe, otherwise we get ghost folders on UW-IMAP CommandPtr cmd = doCommand(imapCommand::clientUnsubscribe(aBox)); completeQueue.removeAll(cmd); cmd = doCommand(imapCommand::clientDelete (aBox)); // If this doesn't work, we try to empty the mailbox first if (cmd->result () != "OK") { completeQueue.removeAll(cmd); if (!assureBox(aBox, false)) return; bool stillOk = true; if (stillOk) { CommandPtr cmd = doCommand( imapCommand::clientStore("1:*", "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); } if (stillOk) { CommandPtr cmd = doCommand(imapCommand::clientClose()); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); setState(ISTATE_LOGIN); } if (stillOk) { CommandPtr cmd = doCommand (imapCommand::clientDelete(aBox)); if (cmd->result () != "OK") stillOk = false; completeQueue.removeAll(cmd); } if (!stillOk) { error (ERR_COULD_NOT_RMDIR, _url.prettyUrl()); return; } } else { completeQueue.removeAll (cmd); } } break; case ITYPE_DIR: { CommandPtr cmd = doCommand (imapCommand::clientDelete (aBox)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_RMDIR, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } break; case ITYPE_MSG: { // if open for read/write if (!assureBox (aBox, false)) return; CommandPtr cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyUrl()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } break; case ITYPE_UNKNOWN: case ITYPE_ATTACH: error (ERR_CANNOT_DELETE, _url.prettyUrl()); break; } finished (); } /* * Copy a mail: data = 'C' + srcURL (KUrl) + destURL (KUrl) * Capabilities: data = 'c'. Result shipped in infoMessage() signal * No-op: data = 'N' * Namespace: data = 'n'. Result shipped in infoMessage() signal * The format is: section=namespace=delimiter * Note that the namespace can be empty * Unsubscribe: data = 'U' + URL (KUrl) * Subscribe: data = 'u' + URL (KUrl) * Change the status: data = 'S' + URL (KUrl) + Flags (QCString) * ACL commands: data = 'A' + command + URL (KUrl) + command-dependent args * AnnotateMore commands: data = 'M' + 'G'et/'S'et + URL + entry + command-dependent args * Search: data = 'E' + URL (KUrl) * Quota commands: data = 'Q' + 'R'oot/'G'et/'S'et + URL + entry + command-dependent args * Custom command: data = 'X' + 'N'ormal/'E'xtended + command + command-dependent args */ void IMAP4Protocol::special (const QByteArray & aData) { kDebug(7116) <<"IMAP4Protocol::special"; if (!makeLogin()) return; QDataStream stream( aData ); int tmp; stream >> tmp; switch (tmp) { case 'C': { // copy KUrl src; KUrl dest; stream >> src >> dest; copy(src, dest, 0, false); break; } case 'c': { // capabilities infoMessage(imapCapabilities.join(" ")); finished(); break; } case 'N': { // NOOP CommandPtr cmd = doCommand(imapCommand::clientNoop()); if (cmd->result () != "OK") { kDebug(7116) <<"NOOP did not succeed - connection broken"; completeQueue.removeAll (cmd); error (ERR_CONNECTION_BROKEN, myHost); return; } completeQueue.removeAll (cmd); finished(); break; } case 'n': { // namespace in the form "section=namespace=delimiter" // entries are separated by , infoMessage( imapNamespaces.join(",") ); finished(); break; } case 'U': { // unsubscribe KUrl _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); CommandPtr cmd = doCommand(imapCommand::clientUnsubscribe(aBox)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Unsubscribe of folder %1 " "failed. The server returned: %2", _url.prettyUrl(), cmd->resultInfo())); return; } completeQueue.removeAll (cmd); finished(); break; } case 'u': { // subscribe KUrl _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); CommandPtr cmd = doCommand(imapCommand::clientSubscribe(aBox)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Subscribe of folder %1 " "failed. The server returned: %2", _url.prettyUrl(), cmd->resultInfo())); return; } completeQueue.removeAll (cmd); finished(); break; } case 'A': { // acl int cmd; stream >> cmd; if ( hasCapability( "ACL" ) ) { specialACLCommand( cmd, stream ); } else { error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("ACL") ); } break; } case 'M': { // annotatemore int cmd; stream >> cmd; if ( hasCapability( "ANNOTATEMORE" ) ) { specialAnnotateMoreCommand( cmd, stream ); } else { error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("ANNOTATEMORE") ); } break; } case 'Q': { // quota int cmd; stream >> cmd; if ( hasCapability( "QUOTA" ) ) { specialQuotaCommand( cmd, stream ); } else { error( ERR_UNSUPPORTED_ACTION, QString::fromLatin1("QUOTA") ); } break; } case 'S': { // status KUrl _url; QByteArray newFlags; stream >> _url >> newFlags; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); if (!assureBox(aBox, false)) return; // make sure we only touch flags we know QByteArray knownFlags = "\\SEEN \\ANSWERED \\FLAGGED \\DRAFT"; const imapInfo info = getSelected(); if ( info.permanentFlagsAvailable() && (info.permanentFlags() & imapInfo::User) ) { knownFlags += " KMAILFORWARDED KMAILTODO KMAILWATCHED KMAILIGNORED $FORWARDED $TODO $WATCHED $IGNORED"; } CommandPtr cmd = doCommand (imapCommand:: clientStore (aSequence, "-FLAGS.SILENT", knownFlags)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Changing the flags of message %1 " "failed with %2.", _url.prettyUrl(), cmd->result())); return; } completeQueue.removeAll (cmd); if (!newFlags.isEmpty()) { cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", newFlags)); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_SLAVE_DEFINED, i18n("Silent Changing the flags of message %1 " "failed with %2.", _url.prettyUrl(), cmd->result())); return; } completeQueue.removeAll (cmd); } finished(); break; } case 's': { // seen KUrl _url; bool seen; QByteArray newFlags; stream >> _url >> seen; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); if ( !assureBox(aBox, true) ) // read-only because changing SEEN should be possible even then return; CommandPtr cmd; if ( seen ) cmd = doCommand( imapCommand::clientStore( aSequence, "+FLAGS.SILENT", "\\SEEN" ) ); else cmd = doCommand( imapCommand::clientStore( aSequence, "-FLAGS.SILENT", "\\SEEN" ) ); if (cmd->result () != "OK") { completeQueue.removeAll (cmd); error(ERR_COULD_NOT_WRITE, i18n( "Changing the flags of message %1 failed.", _url.prettyUrl() ) ); return; } completeQueue.removeAll (cmd); finished(); break; } case 'E': { // search specialSearchCommand( stream ); break; } case 'X': { // custom command specialCustomCommand( stream ); break; } default: kWarning(7116) <<"Unknown command in special():" << tmp; error( ERR_UNSUPPORTED_ACTION, QString(QChar(tmp)) ); break; } } void IMAP4Protocol::specialACLCommand( int command, QDataStream& stream ) { // All commands start with the URL to the box KUrl _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); switch( command ) { case 'S': // SETACL { QString user, acl; stream >> user >> acl; kDebug(7116) <<"SETACL" << aBox << user << acl; CommandPtr cmd = doCommand(imapCommand::clientSetACL(aBox, user, acl)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Setting the Access Control List on folder %1 " "for user %2 failed. The server returned: %3", _url.prettyUrl(), user, cmd->resultInfo())); return; } completeQueue.removeAll (cmd); finished(); break; } case 'D': // DELETEACL { QString user; stream >> user; kDebug(7116) <<"DELETEACL" << aBox << user; CommandPtr cmd = doCommand(imapCommand::clientDeleteACL(aBox, user)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Deleting the Access Control List on folder %1 " "for user %2 failed. The server returned: %3", _url.prettyUrl(), user, cmd->resultInfo())); return; } completeQueue.removeAll (cmd); finished(); break; } case 'G': // GETACL { kDebug(7116) <<"GETACL" << aBox; CommandPtr cmd = doCommand(imapCommand::clientGetACL(aBox)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 " "failed. The server returned: %2", _url.prettyUrl(), cmd->resultInfo())); return; } // Returning information to the application from a special() command isn't easy. // I'm reusing the infoMessage trick seen above (for capabilities), but this // limits me to a string instead of a stringlist. Using DQUOTE as separator, // because it's forbidden in userids by rfc3501 kDebug(7116) << getResults(); infoMessage(getResults().join( "\"" )); finished(); break; } case 'L': // LISTRIGHTS { // Do we need this one? It basically shows which rights are tied together, but that's all? error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); break; } case 'M': // MYRIGHTS { kDebug(7116) <<"MYRIGHTS" << aBox; CommandPtr cmd = doCommand(imapCommand::clientMyRights(aBox)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 " "failed. The server returned: %2", _url.prettyUrl(), cmd->resultInfo())); return; } QStringList lst = getResults(); kDebug(7116) <<"myrights results:" << lst; if ( !lst.isEmpty() ) { Q_ASSERT( lst.count() == 1 ); infoMessage( lst.first() ); } finished(); break; } default: kWarning(7116) <<"Unknown special ACL command:" << command; error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); } } void IMAP4Protocol::specialSearchCommand( QDataStream& stream ) { kDebug(7116) <<"IMAP4Protocol::specialSearchCommand"; KUrl _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); if (!assureBox(aBox, true)) return; CommandPtr cmd = doCommand (imapCommand::clientSearch( aSection )); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Searching of folder %1 " "failed. The server returned: %2", aBox, cmd->resultInfo())); return; } completeQueue.removeAll(cmd); QStringList lst = getResults(); kDebug(7116) <<"IMAP4Protocol::specialSearchCommand '" << aSection << "' returns" << lst; infoMessage( lst.join( " " ) ); finished(); } void IMAP4Protocol::specialCustomCommand( QDataStream& stream ) { kDebug(7116) << "IMAP4Protocol::specialCustomCommand" << endl; QString command, arguments; int type; stream >> type; stream >> command >> arguments; /** * In 'normal' mode we send the command with all information in one go * and retrieve the result. */ if ( type == 'N' ) { kDebug(7116) << "IMAP4Protocol::specialCustomCommand: normal mode" << endl; CommandPtr cmd = doCommand (imapCommand::clientCustom( command, arguments )); if (cmd->result () != "OK") { error( ERR_SLAVE_DEFINED, i18n( "Custom command %1:%2 failed. The server returned: %3", command, arguments, cmd->resultInfo() ) ); return; } completeQueue.removeAll(cmd); QStringList lst = getResults(); kDebug(7116) << "IMAP4Protocol::specialCustomCommand '" << command << ":" << arguments << "' returns " << lst << endl; infoMessage( lst.join( " " ) ); finished(); } else /** * In 'extended' mode we send a first header and push the data of the request in * streaming mode. */ if ( type == 'E' ) { kDebug(7116) << "IMAP4Protocol::specialCustomCommand: extended mode" << endl; CommandPtr cmd = sendCommand (imapCommand::clientCustom( command, QString() )); while ( !parseLoop () ) {}; // see if server is waiting if (!cmd->isComplete () && !getContinuation ().isEmpty ()) { const QByteArray buffer = arguments.toUtf8(); // send data to server bool sendOk = (write (buffer.data (), buffer.size ()) == (ssize_t)buffer.size ()); processedSize( buffer.size() ); if ( !sendOk ) { error ( ERR_CONNECTION_BROKEN, myHost ); completeQueue.removeAll ( cmd ); setState(ISTATE_CONNECT); closeConnection(); return; } } parseWriteLine (""); do { while (!parseLoop ()) {}; } while (!cmd->isComplete ()); completeQueue.removeAll (cmd); QStringList lst = getResults(); kDebug(7116) << "IMAP4Protocol::specialCustomCommand: returns " << lst << endl; infoMessage( lst.join( " " ) ); finished (); } } void IMAP4Protocol::specialAnnotateMoreCommand( int command, QDataStream& stream ) { // All commands start with the URL to the box KUrl _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); switch( command ) { case 'S': // SETANNOTATION { // Params: // KUrl URL of the mailbox // QString entry (should be an actual entry name, no % or *; empty for server entries) // QMap attributes (name and value) QString entry; QMap attributes; stream >> entry >> attributes; kDebug(7116) <<"SETANNOTATION" << aBox << entry << attributes.count() <<" attributes"; CommandPtr cmd = doCommand(imapCommand::clientSetAnnotation(aBox, entry, attributes)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Setting the annotation %1 on folder %2 " " failed. The server returned: %3", entry, _url.prettyUrl(), cmd->resultInfo())); return; } completeQueue.removeAll (cmd); finished(); break; } case 'G': // GETANNOTATION. { // Params: // KUrl URL of the mailbox // QString entry (should be an actual entry name, no % or *; empty for server entries) // QStringList attributes (list of attributes to be retrieved, possibly with % or *) QString entry; QStringList attributeNames; stream >> entry >> attributeNames; kDebug(7116) <<"GETANNOTATION" << aBox << entry << attributeNames; CommandPtr cmd = doCommand(imapCommand::clientGetAnnotation(aBox, entry, attributeNames)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Retrieving the annotation %1 on folder %2 " "failed. The server returned: %3", entry, _url.prettyUrl(), cmd->resultInfo())); return; } // Returning information to the application from a special() command isn't easy. // I'm reusing the infoMessage trick seen above (for capabilities and acls), but this // limits me to a string instead of a stringlist. Let's use \r as separator. kDebug(7116) << getResults(); infoMessage(getResults().join( "\r" )); finished(); break; } default: kWarning(7116) <<"Unknown special annotate command:" << command; error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); } } void IMAP4Protocol::specialQuotaCommand( int command, QDataStream& stream ) { // All commands start with the URL to the box KUrl _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); switch( command ) { case 'R': // GETQUOTAROOT { kDebug(7116) <<"QUOTAROOT" << aBox; CommandPtr cmd = doCommand(imapCommand::clientGetQuotaroot( aBox ) ); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Retrieving the quota root information on folder %1 " "failed. The server returned: %2", _url.prettyUrl(), cmd->resultInfo())); return; } infoMessage(getResults().join( "\r" )); finished(); break; } case 'G': // GETQUOTA { kDebug(7116) <<"GETQUOTA command"; kWarning(7116) <<"UNIMPLEMENTED"; break; } case 'S': // SETQUOTA { kDebug(7116) <<"SETQUOTA command"; kWarning(7116) <<"UNIMPLEMENTED"; break; } default: kWarning(7116) <<"Unknown special quota command:" << command; error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); } } void IMAP4Protocol::rename (const KUrl & src, const KUrl & dest, KIO::JobFlags flags) { kDebug(7116) <<"IMAP4::rename - [" << ((flags & KIO::Overwrite) ?"Overwrite" :"NoOverwrite") <<"]" << src <<" ->" << dest; QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo; QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo; enum IMAP_TYPE sType = parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo, false); enum IMAP_TYPE dType = parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo, false); if (dType == ITYPE_UNKNOWN) { switch (sType) { case ITYPE_BOX: case ITYPE_DIR: case ITYPE_DIR_AND_BOX: { if (getState() == ISTATE_SELECT && sBox == getCurrentBox()) { kDebug(7116) <<"IMAP4::rename - close" << getCurrentBox(); // mailbox can only be renamed if it is closed CommandPtr cmd = doCommand (imapCommand::clientClose()); bool ok = cmd->result() == "OK"; completeQueue.removeAll(cmd); if (!ok) { error(ERR_CANNOT_RENAME, i18n("Unable to close mailbox.")); return; } setState(ISTATE_LOGIN); } CommandPtr cmd = doCommand (imapCommand::clientRename (sBox, dBox)); if (cmd->result () != "OK") { error (ERR_CANNOT_RENAME, cmd->result ()); completeQueue.removeAll (cmd); return; } completeQueue.removeAll (cmd); } break; case ITYPE_MSG: case ITYPE_ATTACH: case ITYPE_UNKNOWN: error (ERR_CANNOT_RENAME, src.prettyUrl()); break; } } else { error (ERR_CANNOT_RENAME, src.prettyUrl()); return; } finished (); } void IMAP4Protocol::slave_status () { bool connected = (getState() != ISTATE_NO) && isConnected(); kDebug(7116) <<"IMAP4::slave_status" << connected; slaveStatus ( connected ? myHost : QString(), connected ); } void IMAP4Protocol::dispatch (int command, const QByteArray & data) { kDebug(7116) <<"IMAP4::dispatch - command=" << command; KIO::TCPSlaveBase::dispatch (command, data); } void IMAP4Protocol::stat (const KUrl & _url) { kDebug(7116) <<"IMAP4::stat -" << _url.prettyUrl(); QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; // parseURL with caching enum IMAP_TYPE aType = parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo, true); UDSEntry entry; entry.insert( UDSEntry::UDS_NAME, aBox); if (!aSection.isEmpty()) { if (getState() == ISTATE_SELECT && aBox == getCurrentBox()) { CommandPtr cmd = doCommand (imapCommand::clientClose()); bool ok = cmd->result() == "OK"; completeQueue.removeAll(cmd); if (!ok) { error(ERR_COULD_NOT_STAT, i18n("Unable to close mailbox.")); return; } setState(ISTATE_LOGIN); } bool ok = false; QString cmdInfo; if (aType == ITYPE_MSG || aType == ITYPE_ATTACH) ok = true; else { CommandPtr cmd = doCommand(imapCommand::clientStatus(aBox, aSection)); ok = cmd->result() == "OK"; cmdInfo = cmd->resultInfo(); completeQueue.removeAll(cmd); } if (!ok) { bool found = false; CommandPtr cmd = doCommand (imapCommand::clientList ("", aBox)); if (cmd->result () == "OK") { for (QList< imapList >::Iterator it = listResponses.begin (); it != listResponses.end (); ++it) { if (aBox == (*it).name ()) found = true; } } completeQueue.removeAll (cmd); if (found) error(ERR_COULD_NOT_STAT, i18n("Unable to get information about folder %1. The server replied: %2", aBox, cmdInfo)); else error(KIO::ERR_DOES_NOT_EXIST, aBox); return; } if ((aSection == "UIDNEXT" && getStatus().uidNextAvailable()) || (aSection == "UNSEEN" && getStatus().unseenAvailable())) { entry.insert( UDSEntry::UDS_SIZE, (aSection == "UIDNEXT") ? getStatus().uidNext() : getStatus().unseen()); } } else if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX || aType == ITYPE_MSG || aType == ITYPE_ATTACH) { ulong validity = 0; // see if the box is already in select/examine state if (aBox == getCurrentBox ()) validity = selectInfo.uidValidity (); else { // do a status lookup on the box // only do this if the box is not selected // the server might change the validity for new select/examine CommandPtr cmd = doCommand (imapCommand::clientStatus (aBox, "UIDVALIDITY")); completeQueue.removeAll (cmd); validity = getStatus ().uidValidity (); } #ifdef __GNUC__ #warning This is temporary since Dec 2000 and makes most of the below code invalid #endif validity = 0; // temporary if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX) { // has no or an invalid uidvalidity if (validity > 0 && validity != aValidity.toULong ()) { //redirect KUrl newUrl = _url; newUrl.setPath ('/' + aBox + ";UIDVALIDITY=" + QString::number(validity)); kDebug(7116) <<"IMAP4::stat - redirecting to" << newUrl.prettyUrl(); redirection (newUrl); } } else if (aType == ITYPE_MSG || aType == ITYPE_ATTACH) { //must determine if this message exists //cause konqueror will check this on paste operations // has an invalid uidvalidity // or no messages in box if (validity > 0 && validity != aValidity.toULong ()) { aType = ITYPE_UNKNOWN; kDebug(7116) <<"IMAP4::stat - url has invalid validity [" << validity <<"d]" << _url.prettyUrl(); } } } entry.insert( UDSEntry::UDS_MIME_TYPE,getMimeType (aType)); //kDebug(7116) <<"IMAP4: stat:" << atom.m_str; switch (aType) { case ITYPE_DIR: entry.insert( UDSEntry::UDS_FILE_TYPE, S_IFDIR); break; case ITYPE_BOX: case ITYPE_DIR_AND_BOX: entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); break; case ITYPE_MSG: case ITYPE_ATTACH: entry.insert(UDSEntry::UDS_FILE_TYPE, S_IFREG); break; case ITYPE_UNKNOWN: error (ERR_DOES_NOT_EXIST, _url.prettyUrl()); break; } statEntry (entry); kDebug(7116) <<"IMAP4::stat - Finishing stat"; finished (); } void IMAP4Protocol::openConnection() { if (makeLogin()) connected(); } void IMAP4Protocol::closeConnection() { if (getState() == ISTATE_NO) return; if (getState() == ISTATE_SELECT && metaData("expunge") == "auto") { CommandPtr cmd = doCommand (imapCommand::clientExpunge()); completeQueue.removeAll (cmd); } if (getState() != ISTATE_CONNECT) { CommandPtr cmd = doCommand (imapCommand::clientLogout()); completeQueue.removeAll (cmd); } disconnectFromHost(); setState(ISTATE_NO); completeQueue.clear(); sentQueue.clear(); lastHandled = 0; currentBox.clear(); readBufferLen = 0; } bool IMAP4Protocol::makeLogin () { if (getState () == ISTATE_LOGIN || getState () == ISTATE_SELECT) return true; kDebug(7116) <<"IMAP4::makeLogin - checking login"; bool alreadyConnected = getState() == ISTATE_CONNECT; kDebug(7116) <<"IMAP4::makeLogin - alreadyConnected" << alreadyConnected; if (alreadyConnected || connectToHost (( mySSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL ), myHost, myPort)) { // fcntl (m_iSock, F_SETFL, (fcntl (m_iSock, F_GETFL) | O_NDELAY)); setState(ISTATE_CONNECT); myAuth = metaData("auth"); myTLS = metaData("tls"); kDebug(7116) <<"myAuth:" << myAuth; CommandPtr cmd; unhandled.clear (); if (!alreadyConnected) while (!parseLoop ()) {} //get greeting QString greeting; if (!unhandled.isEmpty()) greeting = unhandled.first().trimmed(); unhandled.clear (); //get rid of it cmd = doCommand (CommandPtr(new imapCommand ("CAPABILITY", ""))); kDebug(7116) <<"IMAP4: setHost: capability"; for (QStringList::const_iterator it = imapCapabilities.constBegin (); it != imapCapabilities.constEnd (); ++it) { kDebug(7116) <<"'" << (*it) <<"'"; } completeQueue.removeAll (cmd); if (!hasCapability("IMAP4") && !hasCapability("IMAP4rev1")) { error(ERR_COULD_NOT_LOGIN, i18n("The server %1 supports neither " "IMAP4 nor IMAP4rev1.\nIt identified itself with: %2", myHost, greeting)); closeConnection(); return false; } if (metaData("nologin") == "on") return true; if (myTLS == "on" && !hasCapability(QString("STARTTLS"))) { error(ERR_COULD_NOT_LOGIN, i18n("The server does not support TLS.\n" "Disable this security feature to connect unencrypted.")); closeConnection(); return false; } if ((myTLS == "on" /*###|| ( canUseTLS() && myTLS != "off")*/) && hasCapability(QString("STARTTLS"))) { CommandPtr cmd = doCommand (imapCommand::clientStartTLS()); if (cmd->result () == "OK") { completeQueue.removeAll(cmd); if (startSsl()) { kDebug(7116) <<"TLS mode has been enabled."; CommandPtr cmd2 = doCommand (CommandPtr(new imapCommand ("CAPABILITY", ""))); for (QStringList::const_iterator it = imapCapabilities.constBegin (); it != imapCapabilities.constEnd (); ++it) { kDebug(7116) <<"'" << (*it) <<"'"; } completeQueue.removeAll (cmd2); } else { kWarning(7116) <<"TLS mode setup has failed. Aborting."; error (ERR_COULD_NOT_LOGIN, i18n("Starting TLS failed.")); closeConnection(); return false; } } else completeQueue.removeAll(cmd); } if (!myAuth.isEmpty () && myAuth != "*" && !hasCapability (QString ("AUTH=") + myAuth)) { error (ERR_COULD_NOT_LOGIN, i18n("The authentication method %1 is not " "supported by the server.", myAuth)); closeConnection(); return false; } if ( greeting.contains( QRegExp( "Cyrus IMAP4 v2.1" ) ) ) { removeCapability( "ANNOTATEMORE" ); } // starting from Cyrus IMAP 2.3.9, shared seen flags are available QRegExp regExp( "Cyrus\\sIMAP[4]{0,1}\\sv(\\d+)\\.(\\d+)\\.(\\d+)", Qt::CaseInsensitive ); if ( regExp.indexIn( greeting ) >= 0 ) { const int major = regExp.cap( 1 ).toInt(); const int minor = regExp.cap( 2 ).toInt(); const int patch = regExp.cap( 3 ).toInt(); if ( major > 2 || (major == 2 && (minor > 3 || (minor == 3 && patch > 9))) ) { kDebug(7116) << "Cyrus IMAP >= 2.3.9 detected, enabling shared seen flag support"; imapCapabilities.append( "x-kmail-sharedseen" ); } } kDebug(7116) <<"IMAP4::makeLogin - attempting login"; KIO::AuthInfo authInfo; authInfo.username = myUser; authInfo.password = myPass; authInfo.prompt = i18n ("Username and password for your IMAP account:"); kDebug(7116) <<"IMAP4::makeLogin - open_PassDlg said user=" << myUser <<" pass=xx"; QString resultInfo; if (myAuth.isEmpty () || myAuth == "*") { if (myUser.isEmpty () || myPass.isEmpty ()) { if(openPasswordDialog (authInfo)) { myUser = authInfo.username; myPass = authInfo.password; } } if (!clientLogin (myUser, myPass, resultInfo)) error(KIO::ERR_COULD_NOT_AUTHENTICATE, i18n("Unable to login. Probably the " "password is wrong.\nThe server %1 replied:\n%2", myHost, resultInfo)); } else { if (!clientAuthenticate (this, authInfo, myHost, myAuth, mySSL, resultInfo)) error(KIO::ERR_COULD_NOT_AUTHENTICATE, i18n("Unable to authenticate via %1.\n" "The server %2 replied:\n%3", myAuth, myHost, resultInfo)); else { myUser = authInfo.username; myPass = authInfo.password; } } if ( hasCapability("NAMESPACE") ) { // get all namespaces and save the namespace - delimiter association cmd = doCommand( imapCommand::clientNamespace() ); if (cmd->result () == "OK") { kDebug(7116) <<"makeLogin - registered namespaces"; } completeQueue.removeAll (cmd); } // get the default delimiter (empty listing) cmd = doCommand( imapCommand::clientList("", "") ); if (cmd->result () == "OK") { QList< imapList >::Iterator it = listResponses.begin(); if ( it != listResponses.end() ) { namespaceToDelimiter[QString()] = (*it).hierarchyDelimiter(); kDebug(7116) <<"makeLogin - delimiter for empty ns='" << (*it).hierarchyDelimiter() <<"'"; if ( !hasCapability("NAMESPACE") ) { // server does not support namespaces QString nsentry = QString::number( 0 ) + "==" + (*it).hierarchyDelimiter(); imapNamespaces.append( nsentry ); } } } completeQueue.removeAll (cmd); } else { kDebug(7116) <<"makeLogin - NO login"; } return getState() == ISTATE_LOGIN; } void IMAP4Protocol::parseWriteLine (const QString & aStr) { //kDebug(7116) <<"Writing:" << aStr; QByteArray writer = aStr.toUtf8(); int len = writer.length(); // append CRLF if necessary if (len == 0 || (writer[len - 1] != '\n')) { len += 2; writer += "\r\n"; } // write it write(writer.data(), len); } QString IMAP4Protocol::getMimeType (enum IMAP_TYPE aType) { switch (aType) { case ITYPE_DIR: return "inode/directory"; break; case ITYPE_BOX: return "message/digest"; break; case ITYPE_DIR_AND_BOX: return "message/directory"; break; case ITYPE_MSG: return "message/rfc822"; break; // this should be handled by flushOutput case ITYPE_ATTACH: return "application/octet-stream"; break; case ITYPE_UNKNOWN: default: return "unknown/unknown"; } } void IMAP4Protocol::doListEntry (const KUrl & _url, int stretch, imapCache * cache, bool withFlags, bool withSubject) { KUrl aURL = _url; aURL.setQuery (QString()); const QString encodedUrl = aURL.url(KUrl::LeaveTrailingSlash); // utf-8 doListEntry(encodedUrl, stretch, cache, withFlags, withSubject); } void IMAP4Protocol::doListEntry (const QString & encodedUrl, int stretch, imapCache * cache, bool withFlags, bool withSubject) { if (cache) { UDSEntry entry; entry.clear (); const QString uid = QString::number(cache->getUid()); QString tmp = uid; if (stretch > 0) { tmp = "0000000000000000" + uid; tmp = tmp.right (stretch); } if (withSubject) { mailHeader *header = cache->getHeader(); if (header) tmp += ' ' + header->getSubject(); } entry.insert (UDSEntry::UDS_NAME,tmp); tmp = encodedUrl; // utf-8 if (tmp[tmp.length () - 1] != '/') tmp += '/'; tmp += ";UID=" + uid; entry.insert( UDSEntry::UDS_URL, tmp); entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFREG); entry.insert(UDSEntry::UDS_SIZE, cache->getSize()); entry.insert( UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/rfc822")); entry.insert(UDSEntry::UDS_USER,myUser); entry.insert( KIO::UDSEntry::UDS_ACCESS, (withFlags) ? cache->getFlags() : S_IRUSR | S_IXUSR | S_IWUSR); listEntry (entry, false); } } void IMAP4Protocol::doListEntry (const KUrl & _url, const QString & myBox, const imapList & item, bool appendPath) { KUrl aURL = _url; aURL.setQuery (QString()); UDSEntry entry; int hdLen = item.hierarchyDelimiter().length(); { // mailboxName will be appended to the path if appendPath is true QString mailboxName = item.name (); // some beautification if ( mailboxName.startsWith(myBox) && mailboxName.length() > myBox.length()) { mailboxName = mailboxName.right (mailboxName.length () - myBox.length ()); } if (mailboxName[0] == '/') mailboxName = mailboxName.right (mailboxName.length () - 1); if (mailboxName.left(hdLen) == item.hierarchyDelimiter()) mailboxName = mailboxName.right(mailboxName.length () - hdLen); if (mailboxName.right(hdLen) == item.hierarchyDelimiter()) mailboxName.truncate(mailboxName.length () - hdLen); QString tmp; if (!item.hierarchyDelimiter().isEmpty() && mailboxName.contains(item.hierarchyDelimiter()) ) tmp = mailboxName.section(item.hierarchyDelimiter(), -1); else tmp = mailboxName; // konqueror will die with an assertion failure otherwise if (tmp.isEmpty ()) tmp = ".."; if (!tmp.isEmpty ()) { entry.insert(UDSEntry::UDS_NAME,tmp); if (!item.noSelect ()) { if (!item.noInferiors ()) { tmp = "message/directory"; } else { tmp = "message/digest"; } entry.insert(UDSEntry::UDS_MIME_TYPE,tmp); mailboxName += '/'; // explicitly set this as a directory for KFileDialog entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFDIR); } else if (!item.noInferiors ()) { entry.insert(UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); mailboxName += '/'; // explicitly set this as a directory for KFileDialog entry.insert(UDSEntry::UDS_FILE_TYPE,S_IFDIR); } else { entry.insert(UDSEntry::UDS_MIME_TYPE,QString::fromLatin1("unknown/unknown")); } QString path = aURL.path(); if (appendPath) { if (path[path.length() - 1] == '/' && !path.isEmpty() && path != "/") path.truncate(path.length() - 1); if (!path.isEmpty() && path != "/" && path.right(hdLen) != item.hierarchyDelimiter()) { path += item.hierarchyDelimiter(); } path += mailboxName; if (path.toUpper() == "/INBOX/") { // make sure the client can rely on INBOX path = path.toUpper(); } } aURL.setPath(path); tmp = aURL.url(KUrl::LeaveTrailingSlash); // utf-8 entry.insert(UDSEntry::UDS_URL, tmp); entry.insert( UDSEntry::UDS_USER, myUser); entry.insert( UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IWUSR); entry.insert( UDSEntry::UDS_EXTRA,item.attributesAsString()); listEntry (entry, false); } } } enum IMAP_TYPE IMAP4Protocol::parseURL (const KUrl & _url, QString & _box, QString & _section, QString & _type, QString & _uid, QString & _validity, QString & _hierarchyDelimiter, QString & _info, bool cache) { enum IMAP_TYPE retVal; retVal = ITYPE_UNKNOWN; imapParser::parseURL (_url, _box, _section, _type, _uid, _validity, _info); // kDebug(7116) <<"URL: query - '" << KUrl::fromPercentEncoding(_url.query()) <<"'"; // get the delimiter QString myNamespace = namespaceForBox( _box ); kDebug(7116) <<"IMAP4::parseURL - namespace=" << myNamespace; if ( namespaceToDelimiter.contains(myNamespace) ) { _hierarchyDelimiter = namespaceToDelimiter[myNamespace]; kDebug(7116) <<"IMAP4::parseURL - delimiter=" << _hierarchyDelimiter; } if (!_box.isEmpty ()) { kDebug(7116) <<"IMAP4::parseURL - box=" << _box; if (makeLogin ()) { if (getCurrentBox () != _box || _type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK") { if ( cache ) { // assume a normal box retVal = ITYPE_DIR_AND_BOX; } else { // start a listing for the box to get the type CommandPtr cmd; cmd = doCommand (imapCommand::clientList ("", _box)); if (cmd->result () == "OK") { for (QList< imapList >::Iterator it = listResponses.begin (); it != listResponses.end (); ++it) { //kDebug(7116) <<"IMAP4::parseURL - checking" << _box <<" to" << (*it).name(); if (_box == (*it).name ()) { if ( !(*it).hierarchyDelimiter().isEmpty() ) _hierarchyDelimiter = (*it).hierarchyDelimiter(); if ((*it).noSelect ()) { retVal = ITYPE_DIR; } else if ((*it).noInferiors ()) { retVal = ITYPE_BOX; } else { retVal = ITYPE_DIR_AND_BOX; } } } // if we got no list response for the box see if it's a prefix if ( retVal == ITYPE_UNKNOWN && namespaceToDelimiter.contains(_box) ) { retVal = ITYPE_DIR; } } else { kDebug(7116) <<"IMAP4::parseURL - got error for" << _box; } completeQueue.removeAll (cmd); } // cache } else // current == box { retVal = ITYPE_BOX; } } else kDebug(7116) <<"IMAP4::parseURL: no login!"; } else // empty box { // the root is just a dir kDebug(7116) <<"IMAP4: parseURL: box [root]"; retVal = ITYPE_DIR; } // see if it is a real sequence or a simple uid if (retVal == ITYPE_BOX || retVal == ITYPE_DIR_AND_BOX) { if (!_uid.isEmpty ()) { if ( !_uid.contains(':') && !_uid.contains(',') && !_uid.contains('*') ) retVal = ITYPE_MSG; } } if (retVal == ITYPE_MSG) { if ( ( _section.contains("BODY.PEEK[", Qt::CaseInsensitive) || _section.contains("BODY[", Qt::CaseInsensitive) ) && !_section.contains(".MIME") && !_section.contains(".HEADER") ) retVal = ITYPE_ATTACH; } if ( _hierarchyDelimiter.isEmpty() && (_type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK") ) { // this shouldn't happen but when the delimiter is really empty // we try to reconstruct it from the URL if (!_box.isEmpty()) { int start = _url.path().lastIndexOf(_box); if (start != -1) _hierarchyDelimiter = _url.path().mid(start-1, start); kDebug(7116) <<"IMAP4::parseURL - reconstructed delimiter:" << _hierarchyDelimiter << "from URL" << _url.path(); } if (_hierarchyDelimiter.isEmpty()) - _hierarchyDelimiter = "/"; + _hierarchyDelimiter = '/'; } kDebug(7116) <<"IMAP4::parseURL - return" << retVal; return retVal; } int IMAP4Protocol::outputLine (const QByteArray & _str, int len) { if (len == -1) { len = _str.length(); } if (cacheOutput) { if ( !outputBuffer.isOpen() ) { outputBuffer.open(QIODevice::WriteOnly); } outputBuffer.seek( outputBufferIndex ); outputBuffer.write(_str.data(), len); outputBufferIndex += len; return 0; } QByteArray temp; bool relay = relayEnabled; relayEnabled = true; temp = QByteArray::fromRawData (_str.data (), len); parseRelay (temp); temp.clear(); relayEnabled = relay; return 0; } void IMAP4Protocol::flushOutput(const QString &contentEncoding) { // send out cached data to the application if (outputBufferIndex == 0) return; outputBuffer.close(); outputCache.resize(outputBufferIndex); if (decodeContent) { // get the coding from the MIME header QByteArray decoded; - if ( contentEncoding.startsWith("quoted-printable", Qt::CaseInsensitive) ) + if ( contentEncoding.startsWith(QLatin1String("quoted-printable"), Qt::CaseInsensitive) ) decoded = KCodecs::quotedPrintableDecode(outputCache); - else if ( contentEncoding.startsWith("base64", Qt::CaseInsensitive) ) + else if ( contentEncoding.startsWith(QLatin1String("base64"), Qt::CaseInsensitive) ) decoded = QByteArray::fromBase64( outputCache ); else decoded = outputCache; QString mimetype = KMimeType::findByContent( decoded )->name(); kDebug(7116) <<"IMAP4::flushOutput - mimeType" << mimetype; mimeType(mimetype); decodeContent = false; data( decoded ); } else { data( outputCache ); } mProcessedSize += outputBufferIndex; processedSize( mProcessedSize ); outputBufferIndex = 0; outputCache[0] = '\0'; outputBuffer.setBuffer(&outputCache); } ssize_t IMAP4Protocol::myRead(void *data, ssize_t len) { if (readBufferLen) { ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen; memcpy(data, readBuffer, copyLen); readBufferLen -= copyLen; if (readBufferLen) memcpy(readBuffer, &readBuffer[copyLen], readBufferLen); return copyLen; } if (!isConnected()) return 0; waitForResponse( responseTimeout() ); return read((char*)data, len); } bool IMAP4Protocol::assureBox (const QString & aBox, bool readonly) { if (aBox.isEmpty()) return false; CommandPtr cmd; if (aBox != getCurrentBox () || (!getSelected().readWrite() && !readonly)) { // open the box with the appropriate mode kDebug(7116) <<"IMAP4Protocol::assureBox - opening box"; selectInfo = imapInfo(); cmd = doCommand (imapCommand::clientSelect (aBox, readonly)); bool ok = cmd->result() == "OK"; QString cmdInfo = cmd->resultInfo(); completeQueue.removeAll (cmd); if (!ok) { bool found = false; cmd = doCommand (imapCommand::clientList ("", aBox)); if (cmd->result () == "OK") { for (QList< imapList >::Iterator it = listResponses.begin (); it != listResponses.end (); ++it) { if (aBox == (*it).name ()) found = true; } } completeQueue.removeAll (cmd); if (found) { if ( cmdInfo.contains("permission", Qt::CaseInsensitive) ) { // not allowed to enter this folder error(ERR_ACCESS_DENIED, cmdInfo); } else { error(ERR_SLAVE_DEFINED, i18n("Unable to open folder %1. The server replied: %2", aBox, cmdInfo)); } } else { error(KIO::ERR_DOES_NOT_EXIST, aBox); } return false; } } else { // Give the server a chance to deliver updates every ten seconds. // Doing this means a server roundtrip and since assureBox is called // after every mail, we do it with a timeout. kDebug(7116) <<"IMAP4Protocol::assureBox - reusing box"; if ( mTimeOfLastNoop.secsTo( QDateTime::currentDateTime() ) > 10 ) { cmd = doCommand (imapCommand::clientNoop ()); completeQueue.removeAll (cmd); mTimeOfLastNoop = QDateTime::currentDateTime(); kDebug(7116) <<"IMAP4Protocol::assureBox - noop timer fired"; } } // if it is the mode we want if (!getSelected().readWrite() && !readonly) { error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, aBox); return false; } return true; } diff --git a/kioslave/imap4/imapinfo.cpp b/kioslave/imap4/imapinfo.cpp index e9177ff5b..125b888e3 100644 --- a/kioslave/imap4/imapinfo.cpp +++ b/kioslave/imap4/imapinfo.cpp @@ -1,239 +1,239 @@ /********************************************************************** * * imapinfo.cc - IMAP4rev1 SELECT / EXAMINE handler * Copyright (C) 2000 Sven Carstens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Send comments and bug fixes to * *********************************************************************/ /* References: RFC 2060 - Internet Message Access Protocol - Version 4rev1 - December 1996 RFC 2192 - IMAP URL Scheme - September 1997 RFC 1731 - IMAP Authentication Mechanisms - December 1994 (Discusses KERBEROSv4, GSSAPI, and S/Key) RFC 2195 - IMAP/POP AUTHorize Extension for Simple Challenge/Response - September 1997 (CRAM-MD5 authentication method) RFC 2104 - HMAC: Keyed-Hashing for Message Authentication - February 1997 Supported URLs: imap://server/ - Prompt for user/pass, list all folders in home directory imap://user:pass@server/ - Uses LOGIN to log in imap://user;AUTH=method:pass@server/ - Uses AUTHENTICATE to log in imap://server/folder/ - List messages in folder */ #include "imapinfo.h" #include "imapparser.h" #include imapInfo::imapInfo ():count_ (0), recent_ (0), unseen_ (0), uidValidity_ (0), uidNext_ (0), flags_ (0), permanentFlags_ (0), readWrite_ (false), countAvailable_ (false), recentAvailable_ (false), unseenAvailable_ (false), uidValidityAvailable_ (false), uidNextAvailable_ (false), flagsAvailable_ (false), permanentFlagsAvailable_ (false), readWriteAvailable_ (false) { } imapInfo::imapInfo (const imapInfo & mi):count_ (mi.count_), recent_ (mi.recent_), unseen_ (mi.unseen_), uidValidity_ (mi.uidValidity_), uidNext_ (mi.uidNext_), flags_ (mi.flags_), permanentFlags_ (mi.permanentFlags_), readWrite_ (mi.readWrite_), countAvailable_ (mi.countAvailable_), recentAvailable_ (mi.recentAvailable_), unseenAvailable_ (mi.unseenAvailable_), uidValidityAvailable_ (mi.uidValidityAvailable_), uidNextAvailable_ (mi.uidNextAvailable_), flagsAvailable_ (mi.flagsAvailable_), permanentFlagsAvailable_ (mi.permanentFlagsAvailable_), readWriteAvailable_ (mi.readWriteAvailable_) { } imapInfo & imapInfo::operator = (const imapInfo & mi) { // Avoid a = a. if (this == &mi) return *this; count_ = mi.count_; recent_ = mi.recent_; unseen_ = mi.unseen_; uidValidity_ = mi.uidValidity_; uidNext_ = mi.uidNext_; flags_ = mi.flags_; permanentFlags_ = mi.permanentFlags_; readWrite_ = mi.readWrite_; countAvailable_ = mi.countAvailable_; recentAvailable_ = mi.recentAvailable_; unseenAvailable_ = mi.unseenAvailable_; uidValidityAvailable_ = mi.uidValidityAvailable_; uidNextAvailable_ = mi.uidNextAvailable_; flagsAvailable_ = mi.flagsAvailable_; permanentFlagsAvailable_ = mi.permanentFlagsAvailable_; readWriteAvailable_ = mi.readWriteAvailable_; return *this; } imapInfo::imapInfo (const QStringList & list):count_ (0), recent_ (0), unseen_ (0), uidValidity_ (0), uidNext_ (0), flags_ (0), permanentFlags_ (0), readWrite_ (false), countAvailable_ (false), recentAvailable_ (false), unseenAvailable_ (false), uidValidityAvailable_ (false), uidNextAvailable_ (false), flagsAvailable_ (false), permanentFlagsAvailable_ (false), readWriteAvailable_ (false) { for (QStringList::ConstIterator it (list.begin ()); it != list.end (); ++it) { QString line (*it); line.truncate(line.length() - 2); QStringList tokens(line.split (' ', QString::SkipEmptyParts)); kDebug(7116) <<"Processing:" << line; if (tokens[0] != "*") continue; if (tokens[1] == "OK") { if (tokens[2] == "[UNSEEN") setUnseen (tokens[3].left (tokens[3].length () - 1).toULong ()); else if (tokens[2] == "[UIDVALIDITY") setUidValidity (tokens[3].left (tokens[3].length () - 1).toULong ()); else if (tokens[2] == "[UIDNEXT") setUidNext (tokens[3].left (tokens[3].length () - 1).toULong ()); else if (tokens[2] == "[PERMANENTFLAGS") { int flagsStart = line.indexOf('('); int flagsEnd = line.indexOf(')'); kDebug(7116) <<"Checking permFlags from" << flagsStart <<" to" << flagsEnd; if ((-1 != flagsStart) && (-1 != flagsEnd) && flagsStart < flagsEnd) setPermanentFlags (_flags (line.mid (flagsStart, flagsEnd).toLatin1())); } else if (tokens[2] == "[READ-WRITE") { setReadWrite (true); } else if (tokens[2] == "[READ-ONLY") { setReadWrite (false); } else { kDebug(7116) <<"unknown token2:" << tokens[2]; } } else if (tokens[1] == "FLAGS") { int flagsStart = line.indexOf ('('); int flagsEnd = line.indexOf (')'); if ((-1 != flagsStart) && (-1 != flagsEnd) && flagsStart < flagsEnd) setFlags (_flags (line.mid (flagsStart, flagsEnd).toLatin1() )); } else { if (tokens[2] == "EXISTS") setCount (tokens[1].toULong ()); else if (tokens[2] == "RECENT") setRecent (tokens[1].toULong ()); else kDebug(7116) <<"unknown token1/2:" << tokens[1] << tokens[2]; } } } ulong imapInfo::_flags( const QByteArray &inFlags ) { ulong flags = 0; parseString flagsString; flagsString.data = inFlags; if ( flagsString.isEmpty() ) { return flags; } if ( flagsString[0] == '(' ) { flagsString.pos++; } while( !flagsString.isEmpty () && flagsString[0] != ')' ) { QByteArray entry = imapParser::parseOneWord(flagsString).toUpper(); if (entry.isEmpty ()) flagsString.clear(); else if (0 != entry.contains ("\\SEEN")) flags ^= Seen; else if (0 != entry.contains ("\\ANSWERED")) flags ^= Answered; else if (0 != entry.contains ("\\FLAGGED")) flags ^= Flagged; else if (0 != entry.contains ("\\DELETED")) flags ^= Deleted; else if (0 != entry.contains ("\\DRAFT")) flags ^= Draft; else if (0 != entry.contains ("\\RECENT")) flags ^= Recent; else if (0 != entry.contains ("\\*")) flags ^= User; - // non standard kmail falgs + // non standard kmail flags else if ( entry.contains( "KMAILFORWARDED" ) || entry.contains( "$FORWARDED" ) ) flags = flags | Forwarded; else if ( entry.contains( "KMAILTODO" ) || entry.contains( "$TODO" ) ) flags = flags | Todo; else if ( entry.contains( "KMAILWATCHED" ) || entry.contains( "$WATCHED" ) ) flags = flags | Watched; else if ( entry.contains( "KMAILIGNORED" ) || entry.contains( "$IGNORED" ) ) flags = flags | Ignored; } return flags; } diff --git a/kioslave/imap4/imapparser.cpp b/kioslave/imap4/imapparser.cpp index e9df7fca4..d8400b299 100644 --- a/kioslave/imap4/imapparser.cpp +++ b/kioslave/imap4/imapparser.cpp @@ -1,2039 +1,2039 @@ /********************************************************************** * * imapparser.cc - IMAP4rev1 Parser * Copyright (C) 2001-2002 Michael Haeckel * Copyright (C) 2000 Sven Carstens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Send comments and bug fixes to s.carstens@gmx.de * *********************************************************************/ #include "imapparser.h" #include "imapinfo.h" #include "mailheader.h" #include "mimeheader.h" #include "mailaddress.h" #include #include #include #include extern "C" { #include } #include #include #include #include #include #include #include #include #include #include using namespace KIMAP; static sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, NULL, NULL }, { SASL_CB_NOECHOPROMPT, NULL, NULL }, { SASL_CB_GETREALM, NULL, NULL }, { SASL_CB_USER, NULL, NULL }, { SASL_CB_AUTHNAME, NULL, NULL }, { SASL_CB_PASS, NULL, NULL }, { SASL_CB_CANON_USER, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; imapParser::imapParser () { currentState = ISTATE_NO; commandCounter = 0; lastHandled = 0; } imapParser::~imapParser () { delete lastHandled; lastHandled = 0; } CommandPtr imapParser::doCommand (CommandPtr aCmd) { int pl = 0; sendCommand (aCmd); while (pl != -1 && !aCmd->isComplete ()) { while ((pl = parseLoop ()) == 0) ; } return aCmd; } CommandPtr imapParser::sendCommand (CommandPtr aCmd) { aCmd->setId (QString::number(commandCounter++)); sentQueue.append (aCmd); continuation.resize(0); const QString& command = aCmd->command(); if (command == "SELECT" || command == "EXAMINE") { // we need to know which box we are selecting parseString p; p.fromString(aCmd->parameter()); currentBox = parseOneWord(p); kDebug(7116) <<"imapParser::sendCommand - setting current box to" << currentBox; } else if (command == "CLOSE") { // we no longer have a box open currentBox.clear(); } else if (command.contains("SEARCH") || command == "GETACL" || command == "LISTRIGHTS" || command == "MYRIGHTS" || command == "GETANNOTATION" || command == "NAMESPACE" || command == "GETQUOTAROOT" || command == "GETQUOTA" || command == "X-GET-OTHER-USERS" || command == "X-GET-DELEGATES" || command == "X-GET-OUT-OF-OFFICE") { lastResults.clear (); } else if (command == "LIST" || command == "LSUB") { listResponses.clear (); } parseWriteLine (aCmd->getStr ()); return aCmd; } bool imapParser::clientLogin (const QString & aUser, const QString & aPass, QString & resultInfo) { CommandPtr cmd; bool retVal = false; cmd = doCommand ( CommandPtr( new imapCommand ("LOGIN", "\"" + KIMAP::quoteIMAP(aUser) + "\" \"" + KIMAP::quoteIMAP(aPass) + "\"")) ); if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeAll (cmd); return retVal; } static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in ) { kDebug(7116) <<"sasl_interact"; sasl_interact_t *interact = ( sasl_interact_t * ) in; //some mechanisms do not require username && pass, so it doesn't need a popup //window for getting this info for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { if ( interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS ) { if ( ai.username.isEmpty() || ai.password.isEmpty() ) { if (!slave->openPasswordDialog(ai)) return false; } break; } } interact = ( sasl_interact_t * ) in; while( interact->id != SASL_CB_LIST_END ) { kDebug(7116) <<"SASL_INTERACT id:" << interact->id; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kDebug(7116) <<"SASL_CB_[USER|AUTHNAME]: '" << ai.username <<"'"; interact->result = strdup( ai.username.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kDebug(7116) <<"SASL_CB_PASS: [hidden]"; interact->result = strdup( ai.password.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = 0; interact->len = 0; break; } interact++; } return true; } bool imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai, const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo) { bool retVal = false; int result; sasl_conn_t *conn = 0; sasl_interact_t *client_interact = 0; const char *out = 0; uint outlen = 0; const char *mechusing = 0; QByteArray tmp, challenge; kDebug(7116) <<"aAuth:" << aAuth <<" FQDN:" << aFQDN <<" isSSL:" << isSSL; // see if server supports this authenticator if (!hasCapability ("AUTH=" + aAuth)) return false; // result = sasl_client_new( isSSL ? "imaps" : "imap", result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri must be 'imap'. I don't know if it's good or bad. */ aFQDN.toLatin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { kDebug(7116) <<"sasl_client_new failed with:" << result; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); return false; } do { result = sasl_client_start(conn, aAuth.toLatin1(), &client_interact, hasCapability("SASL-IR") ? &out : 0, &outlen, &mechusing); if ( result == SASL_INTERACT ) { if ( !sasl_interact( slave, ai, client_interact ) ) { sasl_dispose( &conn ); return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kDebug(7116) <<"sasl_client_start failed with:" << result; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } CommandPtr cmd; tmp = QByteArray::fromRawData( out, outlen ); challenge = tmp.toBase64(); tmp.clear(); // then lets try it QString firstCommand = aAuth; if ( !challenge.isEmpty() ) { firstCommand += ' '; firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); } cmd = sendCommand (CommandPtr(new imapCommand ("AUTHENTICATE", firstCommand.toLatin1()))); while ( true ) { //read the next line while (parseLoop() == 0) { ; } if ( cmd->isComplete() ) break; if (!continuation.isEmpty()) { // kDebug(7116) <<"S:" << QCString(continuation.data(),continuation.size()+1); if ( continuation.size() > 4 ) { tmp = QByteArray::fromRawData( continuation.data() + 2, continuation.size() - 4 ); challenge = QByteArray::fromBase64( tmp ); // kDebug(7116) <<"S-1:" << QCString(challenge.data(),challenge.size()+1); tmp.clear(); } do { result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), challenge.size(), &client_interact, &out, &outlen); if (result == SASL_INTERACT) { if ( !sasl_interact( slave, ai, client_interact ) ) { sasl_dispose( &conn ); return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kDebug(7116) <<"sasl_client_step failed with:" << result; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } tmp = QByteArray::fromRawData( out, outlen ); // kDebug(7116) <<"C-1:" << QCString(tmp.data(),tmp.size()+1); challenge = tmp.toBase64(); tmp.clear(); // kDebug(7116) <<"C:" << QCString(challenge.data(),challenge.size()+1); parseWriteLine (challenge); continuation.resize(0); } } if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeAll (cmd); sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection. return retVal; } void imapParser::parseUntagged (parseString & result) { //kDebug(7116) <<"imapParser::parseUntagged - '" << result.cstr() <<"'"; parseOneWord(result); // * QByteArray what = parseLiteral (result); // see whats coming next switch (what[0]) { //the status responses case 'B': // BAD or BYE if (qstrncmp(what, "BAD", what.size()) == 0) { parseResult (what, result); } else if (qstrncmp(what, "BYE", what.size()) == 0) { parseResult (what, result); if ( sentQueue.count() ) { // BYE that interrupts a command -> copy the reason for it CommandPtr current = sentQueue.at (0); current->setResultInfo(result.cstr()); } currentState = ISTATE_NO; } break; case 'N': // NO if (what[1] == 'O' && what.size() == 2) { parseResult (what, result); } else if (qstrncmp(what, "NAMESPACE", what.size()) == 0) { parseNamespace (result); } break; case 'O': // OK if (what[1] == 'K' && what.size() == 2) { parseResult (what, result); } else if (qstrncmp(what, "OTHER-USER", 10) == 0) { // X-GET-OTHER-USER parseOtherUser (result); } else if (qstrncmp(what, "OUT-OF-OFFICE", 13) == 0) { // X-GET-OUT-OF-OFFICE parseOutOfOffice (result); } break; case 'D': if (qstrncmp(what, "DELEGATE", 8) == 0) { // X-GET-DELEGATES parseDelegate (result); } break; case 'P': // PREAUTH if (qstrncmp(what, "PREAUTH", what.size()) == 0) { parseResult (what, result); currentState = ISTATE_LOGIN; } break; // parse the other responses case 'C': // CAPABILITY if (qstrncmp(what, "CAPABILITY", what.size()) == 0) { parseCapability (result); } break; case 'F': // FLAGS if (qstrncmp(what, "FLAGS", what.size()) == 0) { parseFlags (result); } break; case 'L': // LIST or LSUB or LISTRIGHTS if (qstrncmp(what, "LIST", what.size()) == 0) { parseList (result); } else if (qstrncmp(what, "LSUB", what.size()) == 0) { parseLsub (result); } else if (qstrncmp(what, "LISTRIGHTS", what.size()) == 0) { parseListRights (result); } break; case 'M': // MYRIGHTS if (qstrncmp(what, "MYRIGHTS", what.size()) == 0) { parseMyRights (result); } break; case 'S': // SEARCH or STATUS if (qstrncmp(what, "SEARCH", what.size()) == 0) { parseSearch (result); } else if (qstrncmp(what, "STATUS", what.size()) == 0) { parseStatus (result); } break; case 'A': // ACL or ANNOTATION if (qstrncmp(what, "ACL", what.size()) == 0) { parseAcl (result); } else if (qstrncmp(what, "ANNOTATION", what.size()) == 0) { parseAnnotation (result); } break; case 'Q': // QUOTA or QUOTAROOT if ( what.size() > 5 && qstrncmp(what, "QUOTAROOT", what.size()) == 0) { parseQuotaRoot( result ); } else if (qstrncmp(what, "QUOTA", what.size()) == 0) { parseQuota( result ); } break; case 'X': // Custom command { parseCustom( result ); } break; default: //better be a number { ulong number; bool valid; number = what.toUInt(&valid); if (valid) { what = parseLiteral (result); switch (what[0]) { case 'E': if (qstrncmp(what, "EXISTS", what.size()) == 0) { parseExists (number, result); } else if (qstrncmp(what, "EXPUNGE", what.size()) == 0) { parseExpunge (number, result); } break; case 'F': if (qstrncmp(what, "FETCH", what.size()) == 0) { seenUid.clear(); parseFetch (number, result); } break; case 'S': if (qstrncmp(what, "STORE", what.size()) == 0) // deprecated store { seenUid.clear(); parseFetch (number, result); } break; case 'R': if (qstrncmp(what, "RECENT", what.size()) == 0) { parseRecent (number, result); } break; default: break; } } } break; } //switch } //func void imapParser::parseResult (QByteArray & result, parseString & rest, const QString & command) { if (command == "SELECT") selectInfo.setReadWrite(true); if (rest[0] == '[') { rest.pos++; QByteArray option = parseOneWord(rest, true); switch (option[0]) { case 'A': // ALERT if (option == "ALERT") { rest.pos = rest.data.indexOf(']', rest.pos) + 1; // The alert text is after [ALERT]. // Is this correct or do we need to care about litterals? selectInfo.setAlert( rest.cstr() ); } break; case 'N': // NEWNAME if (option == "NEWNAME") { } break; case 'P': //PARSE or PERMANENTFLAGS if (option == "PARSE") { } else if (option == "PERMANENTFLAGS") { uint end = rest.data.indexOf(']', rest.pos); QByteArray flags(rest.data.data() + rest.pos, end - rest.pos); selectInfo.setPermanentFlags (flags); rest.pos = end; } break; case 'R': //READ-ONLY or READ-WRITE if (option == "READ-ONLY") { selectInfo.setReadWrite (false); } else if (option == "READ-WRITE") { selectInfo.setReadWrite (true); } break; case 'T': //TRYCREATE if (option == "TRYCREATE") { } break; case 'U': //UIDVALIDITY or UNSEEN if (option == "UIDVALIDITY") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUidValidity (value); } else if (option == "UNSEEN") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUnseen (value); } else if (option == "UIDNEXT") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUidNext (value); } else break; } if (rest[0] == ']') rest.pos++; //tie off ] skipWS (rest); } if (command.isEmpty()) { // This happens when parsing an intermediate result line (those that start with '*'). // No state change involved, so we can stop here. return; } switch (command[0].toLatin1 ()) { case 'A': if (command == "AUTHENTICATE") if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_LOGIN; break; case 'L': if (command == "LOGIN") if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_LOGIN; break; case 'E': if (command == "EXAMINE") { if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_SELECT; else { if (currentState == ISTATE_SELECT) currentState = ISTATE_LOGIN; currentBox.clear(); } kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox; } break; case 'S': if (command == "SELECT") { if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_SELECT; else { if (currentState == ISTATE_SELECT) currentState = ISTATE_LOGIN; currentBox.clear(); } kDebug(7116) <<"imapParser::parseResult - current box is now" << currentBox; } break; default: break; } } void imapParser::parseCapability (parseString & result) { QByteArray data = result.cstr(); kAsciiToLower( data.data() ); imapCapabilities = QString::fromLatin1(data).split ( ' ', QString::SkipEmptyParts ); } void imapParser::parseFlags (parseString & result) { selectInfo.setFlags(result.cstr()); } void imapParser::parseList (parseString & result) { imapList this_one; if (result[0] != '(') return; //not proper format for us result.pos++; // tie off ( this_one.parseAttributes( result ); result.pos++; // tie off ) skipWS (result); this_one.setHierarchyDelimiter(parseLiteral(result)); this_one.setName(QString::fromUtf8(KIMAP::decodeImapFolderName( parseLiteral(result)))); // decode modified UTF7 listResponses.append (this_one); } void imapParser::parseLsub (parseString & result) { imapList this_one (result.cstr(), *this); listResponses.append (this_one); } void imapParser::parseListRights (parseString & result) { parseOneWord (result); // skip mailbox name parseOneWord (result); // skip user id while ( true ) { const QByteArray word = parseOneWord (result); if ( word.isEmpty() ) break; lastResults.append (word); } } void imapParser::parseAcl (parseString & result) { parseOneWord (result); // skip mailbox name // The result is user1 perm1 user2 perm2 etc. The caller will sort it out. while ( !result.isEmpty() ) { const QByteArray word = parseLiteral(result); if ( word.isEmpty() ) break; lastResults.append (word); } } void imapParser::parseAnnotation (parseString & result) { parseOneWord (result); // skip mailbox name skipWS (result); parseOneWord (result); // skip entry name (we know it since we don't allow wildcards in it) skipWS (result); if (result.isEmpty() || result[0] != '(') return; result.pos++; skipWS (result); // The result is name1 value1 name2 value2 etc. The caller will sort it out. while ( !result.isEmpty() && result[0] != ')' ) { const QByteArray word = parseLiteral (result); if ( word.isEmpty() ) break; lastResults.append (word); } } void imapParser::parseQuota (parseString & result) { // quota_response ::= "QUOTA" SP astring SP quota_list // quota_list ::= "(" #quota_resource ")" // quota_resource ::= atom SP number SP number QByteArray root = parseOneWord( result ); if ( root.isEmpty() ) { lastResults.append( "" ); } else { lastResults.append( root ); } if (result.isEmpty() || result[0] != '(') return; result.pos++; skipWS (result); QStringList triplet; while ( !result.isEmpty() && result[0] != ')' ) { const QByteArray word = parseLiteral(result); if ( word.isEmpty() ) break; triplet.append(word); } lastResults.append( triplet.join(" ") ); } void imapParser::parseQuotaRoot (parseString & result) { // quotaroot_response // ::= "QUOTAROOT" SP astring *(SP astring) parseOneWord (result); // skip mailbox name skipWS (result); if ( result.isEmpty() ) return; QStringList roots; while ( !result.isEmpty() ) { const QByteArray word = parseLiteral (result); if ( word.isEmpty() ) break; roots.append (word); } lastResults.append( roots.isEmpty() ? "" : roots.join( " " ) ); } void imapParser::parseCustom (parseString & result) { QByteArray word = parseLiteral (result, false, false); lastResults.append( word ); } void imapParser::parseOtherUser (parseString & result) { lastResults.append( parseOneWord ( result ) ); } void imapParser::parseDelegate (parseString & result) { const QString email = parseOneWord ( result ); QStringList rights; while ( !result.isEmpty() ) { QByteArray word = parseLiteral ( result, false, false ); rights.append( word ); } lastResults.append( email + ':' + rights.join( "," ) ); } void imapParser::parseOutOfOffice (parseString & result) { const QString state = parseOneWord (result); parseOneWord (result); // skip encoding QByteArray msg = parseLiteral (result, false, false); lastResults.append( state + '^' + QString::fromUtf8( msg ) ); } void imapParser::parseMyRights (parseString & result) { parseOneWord (result); // skip mailbox name Q_ASSERT( lastResults.isEmpty() ); // we can only be called once lastResults.append (parseOneWord (result) ); } void imapParser::parseSearch (parseString & result) { ulong value; while (parseOneNumber (result, value)) { lastResults.append (QString::number(value)); } } void imapParser::parseStatus (parseString & inWords) { lastStatus = imapInfo (); parseLiteral(inWords); // swallow the box if (inWords[0] != '(') return; inWords.pos++; skipWS (inWords); while (!inWords.isEmpty() && inWords[0] != ')') { ulong value; QByteArray label = parseOneWord(inWords); if (parseOneNumber (inWords, value)) { if (label == "MESSAGES") lastStatus.setCount (value); else if (label == "RECENT") lastStatus.setRecent (value); else if (label == "UIDVALIDITY") lastStatus.setUidValidity (value); else if (label == "UNSEEN") lastStatus.setUnseen (value); else if (label == "UIDNEXT") lastStatus.setUidNext (value); } } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); } void imapParser::parseExists (ulong value, parseString & result) { selectInfo.setCount (value); result.pos = result.data.size(); } void imapParser::parseExpunge (ulong value, parseString & result) { Q_UNUSED(value); Q_UNUSED(result); } void imapParser::parseAddressList (parseString & inWords, QList& list) { if ( inWords.isEmpty() ) return; if (inWords[0] != '(') { parseOneWord (inWords); // parse NIL } else { inWords.pos++; skipWS (inWords); while (!inWords.isEmpty () && inWords[0] != ')') { if (inWords[0] == '(') { mailAddress *addr = new mailAddress; parseAddress(inWords, *addr); list.append(addr); } else { break; } } if (!inWords.isEmpty() && inWords[0] == ')') inWords.pos++; skipWS (inWords); } } const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal) { inWords.pos++; skipWS (inWords); retVal.setFullName(parseLiteral(inWords)); retVal.setCommentRaw(parseLiteral(inWords)); retVal.setUser(parseLiteral(inWords)); retVal.setHost(parseLiteral(inWords)); if (!inWords.isEmpty() && inWords[0] == ')') inWords.pos++; skipWS (inWords); return retVal; } mailHeader * imapParser::parseEnvelope (parseString & inWords) { mailHeader *envelope = 0; if (inWords[0] != '(') return envelope; inWords.pos++; skipWS (inWords); envelope = new mailHeader; //date envelope->setDate(parseLiteral(inWords)); //subject envelope->setSubject(parseLiteral(inWords)); QList list; //from parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setFrom(*list.last()); list.clear(); } //sender parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setSender(*list.last()); list.clear(); } //reply-to parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setReplyTo(*list.last()); list.clear(); } //to parseAddressList (inWords, envelope->to()); //cc parseAddressList (inWords, envelope->cc()); //bcc parseAddressList (inWords, envelope->bcc()); //in-reply-to envelope->setInReplyTo(parseLiteral(inWords)); //message-id envelope->setMessageId(parseLiteral(inWords)); // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteral (inWords); } if (!inWords.isEmpty() && inWords[0] == ')') inWords.pos++; skipWS (inWords); return envelope; } // parse parameter pairs into a dictionary // caller must clean up the dictionary items QHash < QByteArray, QString > imapParser::parseDisposition (parseString & inWords) { QByteArray disposition; QHash < QByteArray, QString > retVal; if (inWords[0] != '(') { //disposition only disposition = parseOneWord (inWords); } else { inWords.pos++; skipWS (inWords); //disposition disposition = parseOneWord (inWords); retVal = parseParameters (inWords); if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } if (!disposition.isEmpty ()) { retVal.insert ("content-disposition", QString(disposition)); } return retVal; } // parse parameter pairs into a dictionary // caller must clean up the dictionary items QHash < QByteArray, QString > imapParser::parseParameters (parseString & inWords) { QHash < QByteArray, QString > retVal; if (inWords[0] != '(') { //better be NIL parseOneWord (inWords); } else { inWords.pos++; skipWS (inWords); while (!inWords.isEmpty () && inWords[0] != ')') { const QByteArray l1 = parseLiteral(inWords); const QByteArray l2 = parseLiteral(inWords); retVal.insert (l1.toLower(), QString(l2)); } if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } return retVal; } mimeHeader * imapParser::parseSimplePart (parseString & inWords, QString & inSection, mimeHeader * localPart) { QByteArray subtype; QByteArray typeStr; QHash < QByteArray, QString > parameters; ulong size; if (inWords[0] != '(') return 0; if (!localPart) localPart = new mimeHeader; localPart->setPartSpecifier (inSection); inWords.pos++; skipWS (inWords); //body type typeStr = parseLiteral(inWords); //body subtype subtype = parseLiteral(inWords); localPart->setType (typeStr + '/' + subtype); //body parameter parenthesized list parameters = parseParameters (inWords); { QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setTypeParm (it.key (), it.value ()); } parameters.clear (); } //body id localPart->setID (parseLiteral(inWords)); //body description localPart->setDescription (parseLiteral(inWords)); //body encoding localPart->setEncoding (parseLiteral(inWords)); //body size if (parseOneNumber (inWords, size)) localPart->setLength (size); // type specific extensions if (localPart->getType().toUpper() == "MESSAGE/RFC822") { //envelope structure mailHeader *envelope = parseEnvelope (inWords); //body structure parseBodyStructure (inWords, inSection, envelope); localPart->setNestedMessage (envelope); //text lines ulong lines; parseOneNumber (inWords, lines); } else { if (typeStr == "TEXT") { //text lines ulong lines; parseOneNumber (inWords, lines); } // md5 parseLiteral(inWords); // body disposition parameters = parseDisposition (inWords); { QString disposition = parameters["content-disposition"]; localPart->setDisposition (disposition.toAscii ()); QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setDispositionParm (it.key (), it.value ()); } parameters.clear (); } // body language parseSentence (inWords); } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteral(inWords); } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return localPart; } mimeHeader * imapParser::parseBodyStructure (parseString & inWords, QString & inSection, mimeHeader * localPart) { bool init = false; if (inSection.isEmpty()) { // first run init = true; // assume one part - inSection = "1"; + inSection = '1'; } int section = 0; if (inWords[0] != '(') { // skip "" parseOneWord (inWords); return 0; } inWords.pos++; skipWS (inWords); if (inWords[0] == '(') { QByteArray subtype; QHash< QByteArray, QString > parameters; QString outSection; if (!localPart) localPart = new mimeHeader; else { // might be filled from an earlier run localPart->clearNestedParts (); localPart->clearTypeParameters (); localPart->clearDispositionParameters (); // an envelope was passed in so this is the multipart header outSection = inSection + ".HEADER"; } if (inWords[0] == '(' && init) - inSection = "0"; + inSection = '0'; // set the section if ( !outSection.isEmpty() ) { localPart->setPartSpecifier(outSection); } else { localPart->setPartSpecifier(inSection); } // is multipart (otherwise it is a simplepart and handled later) while (inWords[0] == '(') { outSection = QString::number(++section); if (!init) outSection = inSection + '.' + outSection; mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0); localPart->addNestedPart (subpart); } // fetch subtype subtype = parseOneWord (inWords); localPart->setType ("MULTIPART/" + subtype); // fetch parameters parameters = parseParameters (inWords); { QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setTypeParm (it.key (), it.value ()); } parameters.clear (); } // body disposition parameters = parseDisposition (inWords); { QString disposition = parameters["content-disposition"]; localPart->setDisposition (disposition.toAscii ()); QHashIterator < QByteArray, QString > it (parameters); while (it.hasNext ()) { it.next(); localPart->setDispositionParm (it.key (), it.value ()); } parameters.clear (); } // body language parseSentence (inWords); } else { // is simple part inWords.pos--; inWords.data[inWords.pos] = '('; //fake a sentence if ( localPart ) inSection = inSection + ".1"; localPart = parseSimplePart (inWords, inSection, localPart); inWords.pos--; inWords.data[inWords.pos] = ')'; //remove fake } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteral(inWords); } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return localPart; } void imapParser::parseBody (parseString & inWords) { // see if we got a part specifier if (inWords[0] == '[') { QByteArray specifier; QByteArray label; inWords.pos++; specifier = parseOneWord (inWords, true); if (inWords[0] == '(') { inWords.pos++; while (!inWords.isEmpty () && inWords[0] != ')') { label = parseOneWord (inWords); } if (inWords[0] == ')') inWords.pos++; } if (inWords[0] == ']') inWords.pos++; skipWS (inWords); // parse the header if (qstrncmp(specifier, "0", specifier.size()) == 0) { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); // don't know where to put it, throw it away parseLiteral(inWords, true); } else { kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii (); // fill it up with data QString theHeader = parseLiteral(inWords, true); mimeIOQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); } } else if (qstrncmp(specifier, "HEADER.FIELDS", specifier.size()) == 0) { // BODY[HEADER.FIELDS (References)] {n} //kDebug(7116) <<"imapParser::parseBody - HEADER.FIELDS:" // << QCString(label.data(), label.size()+1); if (qstrncmp(label, "REFERENCES", label.size()) == 0) { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); // don't know where to put it, throw it away parseLiteral (inWords, true); } else { QByteArray references = parseLiteral(inWords, true); int start = references.indexOf ('<'); int end = references.lastIndexOf ('>'); if (start < end) references = references.mid (start, end - start + 1); envelope->setReferences(references.simplified()); } } else { // not a header we care about throw it away parseLiteral(inWords, true); } } else { if (specifier.contains(".MIME") ) { mailHeader *envelope = new mailHeader; QString theHeader = parseLiteral(inWords, false); mimeIOQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); if (lastHandled) lastHandled->setHeader (envelope); return; } // throw it away kDebug(7116) <<"imapParser::parseBody - discarding" << seenUid.toAscii (); parseLiteral(inWords, true); } } else // no part specifier { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kDebug(7116) <<"imapParser::parseBody - discarding" << envelope << seenUid.toAscii (); // don't know where to put it, throw it away parseSentence (inWords); } else { kDebug(7116) <<"imapParser::parseBody - reading" << envelope << seenUid.toAscii (); // fill it up with data QString section; mimeHeader *body = parseBodyStructure (inWords, section, envelope); if (body != envelope) delete body; } } } void imapParser::parseFetch (ulong /* value */, parseString & inWords) { if (inWords[0] != '(') return; inWords.pos++; skipWS (inWords); delete lastHandled; lastHandled = 0; while (!inWords.isEmpty () && inWords[0] != ')') { if (inWords[0] == '(') parseSentence (inWords); else { const QByteArray word = parseLiteral(inWords, false, true); switch (word[0]) { case 'E': if (word == "ENVELOPE") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); else lastHandled = new imapCache(); if (envelope && !envelope->getMessageId ().isEmpty ()) { // we have seen this one already // or don't know where to put it parseSentence (inWords); } else { envelope = parseEnvelope (inWords); if (envelope) { envelope->setPartSpecifier (seenUid + ".0"); lastHandled->setHeader (envelope); lastHandled->setUid (seenUid.toULong ()); } } } break; case 'B': if (word == "BODY") { parseBody (inWords); } else if (word == "BODY[]" ) { // Do the same as with "RFC822" parseLiteral(inWords, true); } else if (word == "BODYSTRUCTURE") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); // fill it up with data QString section; mimeHeader *body = parseBodyStructure (inWords, section, envelope); QByteArray data; QDataStream stream( &data, QIODevice::WriteOnly ); if ( body ) body->serialize(stream); parseRelay(data); delete body; } break; case 'U': if (word == "UID") { seenUid = parseOneWord(inWords); mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); else lastHandled = new imapCache(); if (seenUid.isEmpty ()) { // unknown what to do kDebug(7116) <<"imapParser::parseFetch - UID empty"; } else { lastHandled->setUid (seenUid.toULong ()); } if (envelope) envelope->setPartSpecifier (seenUid); } break; case 'R': if (word == "RFC822.SIZE") { ulong size; parseOneNumber (inWords, size); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setSize (size); } - else if (word.startsWith("RFC822")) + else if (word.startsWith("RFC822")) //krazy:exclude=strings { // might be RFC822 RFC822.TEXT RFC822.HEADER parseLiteral(inWords, true); } break; case 'I': if (word == "INTERNALDATE") { const QByteArray date = parseOneWord(inWords); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setDate(date); } break; case 'F': if (word == "FLAGS") { //kDebug(7116) <<"GOT FLAGS" << inWords.cstr(); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setFlags (imapInfo::_flags (inWords.cstr())); } break; default: parseLiteral(inWords); break; } } } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteral(inWords); } if (inWords.isEmpty() || inWords[0] != ')') return; inWords.pos++; skipWS (inWords); } // default parser void imapParser::parseSentence (parseString & inWords) { bool first = true; int stack = 0; //find the first nesting parentheses while (!inWords.isEmpty () && (stack != 0 || first)) { first = false; skipWS (inWords); unsigned char ch = inWords[0]; switch (ch) { case '(': inWords.pos++; ++stack; break; case ')': inWords.pos++; --stack; break; case '[': inWords.pos++; ++stack; break; case ']': inWords.pos++; --stack; break; default: parseLiteral(inWords); skipWS (inWords); break; } } skipWS (inWords); } void imapParser::parseRecent (ulong value, parseString & result) { selectInfo.setRecent (value); result.pos = result.data.size(); } void imapParser::parseNamespace (parseString & result) { if ( result[0] != '(' ) return; QString delimEmpty; if ( namespaceToDelimiter.contains( QString() ) ) delimEmpty = namespaceToDelimiter[QString()]; namespaceToDelimiter.clear(); imapNamespaces.clear(); // remember what section we're in (user, other users, shared) int ns = -1; bool personalAvailable = false; while ( !result.isEmpty() ) { if ( result[0] == '(' ) { result.pos++; // tie off ( if ( result[0] == '(' ) { // new namespace section result.pos++; // tie off ( ++ns; } // namespace prefix QString prefix = QString::fromLatin1( parseOneWord( result ) ); // delimiter QString delim = QString::fromLatin1( parseOneWord( result ) ); kDebug(7116) <<"imapParser::parseNamespace ns='" << prefix <<"',delim='" << delim <<"'"; if ( ns == 0 ) { // at least one personal ns personalAvailable = true; } QString nsentry = QString::number( ns ) + '=' + prefix + '=' + delim; imapNamespaces.append( nsentry ); if ( prefix.right( 1 ) == delim ) { // strip delimiter to get a correct entry for comparisons prefix.resize( prefix.length() ); } namespaceToDelimiter[prefix] = delim; result.pos++; // tie off ) skipWS( result ); } else if ( result[0] == ')' ) { result.pos++; // tie off ) skipWS( result ); } else if ( result[0] == 'N' ) { // drop NIL ++ns; parseOneWord( result ); } else { // drop whatever it is parseOneWord( result ); } } if ( !delimEmpty.isEmpty() ) { // remember default delimiter namespaceToDelimiter[QString()] = delimEmpty; if ( !personalAvailable ) { // at least one personal ns would be nice kDebug(7116) <<"imapParser::parseNamespace - registering own personal ns"; QString nsentry = "0==" + delimEmpty; imapNamespaces.append( nsentry ); } } } int imapParser::parseLoop () { parseString result; if (!parseReadLine(result.data)) return -1; //kDebug(7116) << result.cstr(); // includes \n if (result.data.isEmpty()) return 0; if (!sentQueue.count ()) { // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE kDebug(7116) <<"imapParser::parseLoop - unhandledResponse:" << result.cstr(); unhandled << result.cstr(); } else { CommandPtr current = sentQueue.at (0); switch (result[0]) { case '*': result.data.resize(result.data.size() - 2); // tie off CRLF parseUntagged (result); break; case '+': continuation = result.data; break; default: { QByteArray tag = parseLiteral(result); if (current->id() == tag.data()) { result.data.resize(result.data.size() - 2); // tie off CRLF QByteArray resultCode = parseLiteral (result); //the result current->setResult (resultCode); current->setResultInfo(result.cstr()); current->setComplete (); sentQueue.removeAll (current); completeQueue.append (current); if (result.length()) parseResult (resultCode, result, current->command()); } else { kDebug(7116) <<"imapParser::parseLoop - unknown tag '" << tag <<"'"; QByteArray cstr = tag + ' ' + result.cstr(); result.data = cstr; result.pos = 0; result.data.resize(cstr.length()); } } break; } } return 1; } void imapParser::parseRelay (const QByteArray & buffer) { Q_UNUSED(buffer); qWarning ("imapParser::parseRelay - virtual function not reimplemented - data lost"); } void imapParser::parseRelay (ulong len) { Q_UNUSED(len); qWarning ("imapParser::parseRelay - virtual function not reimplemented - announcement lost"); } bool imapParser::parseRead (QByteArray & buffer, long len, long relay) { Q_UNUSED(buffer); Q_UNUSED(len); Q_UNUSED(relay); qWarning ("imapParser::parseRead - virtual function not reimplemented - no data read"); return false; } bool imapParser::parseReadLine (QByteArray & buffer, long relay) { Q_UNUSED(buffer); Q_UNUSED(relay); qWarning ("imapParser::parseReadLine - virtual function not reimplemented - no data read"); return false; } void imapParser::parseWriteLine (const QString & str) { Q_UNUSED(str); qWarning ("imapParser::parseWriteLine - virtual function not reimplemented - no data written"); } void imapParser::parseURL (const KUrl & _url, QString & _box, QString & _section, QString & _type, QString & _uid, QString & _validity, QString & _info) { QStringList parameters; _box = _url.path (); kDebug(7116) <<"imapParser::parseURL" << _box; int paramStart = _box.indexOf("/;"); if ( paramStart > -1 ) { QString paramString = _box.right( _box.length() - paramStart-2 ); parameters = paramString.split (';', QString::SkipEmptyParts); //split parameters _box.truncate( paramStart ); // strip parameters } // extract parameters for (QStringList::ConstIterator it (parameters.constBegin ()); it != parameters.constEnd (); ++it) { QString temp = (*it); // if we have a '/' separator we'll just nuke it int pt = temp.indexOf ('/'); if (pt > 0) temp.truncate(pt); - if (temp.startsWith("section=", Qt::CaseInsensitive)) + if (temp.startsWith(QLatin1String("section="), Qt::CaseInsensitive)) _section = temp.right (temp.length () - 8); - else if (temp.startsWith("type=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("type="), Qt::CaseInsensitive)) _type = temp.right (temp.length () - 5); - else if (temp.startsWith("uid=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("uid="), Qt::CaseInsensitive)) _uid = temp.right (temp.length () - 4); - else if (temp.startsWith("uidvalidity=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("uidvalidity="), Qt::CaseInsensitive)) _validity = temp.right (temp.length () - 12); - else if (temp.startsWith("info=", Qt::CaseInsensitive)) + else if (temp.startsWith(QLatin1String("info="), Qt::CaseInsensitive)) _info = temp.right (temp.length () - 5); } // kDebug(7116) <<"URL: section=" << _section <<", type=" << _type <<", uid=" << _uid; // kDebug(7116) <<"URL: user()" << _url.user(); // kDebug(7116) <<"URL: path()" << _url.path(); // kDebug(7116) <<"URL: encodedPathAndQuery()" << _url.encodedPathAndQuery(); if (!_box.isEmpty ()) { // strip / if (_box[0] == '/') _box = _box.right (_box.length () - 1); if (!_box.isEmpty () && _box[_box.length () - 1] == '/') _box.truncate(_box.length() - 1); } kDebug(7116) <<"URL: box=" << _box <<", section=" << _section <<", type=" << _type << ", uid=" << _uid << ", validity=" << _validity << ", info=" << _info; } QByteArray imapParser::parseLiteral(parseString & inWords, bool relay, bool stopAtBracket) { if (!inWords.isEmpty() && inWords[0] == '{') { QByteArray retVal; int runLen = inWords.find ('}', 1); if (runLen > 0) { bool proper; long runLenSave = runLen + 1; QByteArray tmpstr(runLen, '\0'); inWords.takeMidNoResize(tmpstr, 1, runLen - 1); runLen = tmpstr.toULong (&proper); inWords.pos += runLenSave; if (proper) { //now get the literal from the server if (relay) parseRelay (runLen); QByteArray rv; parseRead (rv, runLen, relay ? runLen : 0); rv.resize(qMax(runLen, rv.size())); // what's the point? retVal = rv; inWords.clear(); parseReadLine (inWords.data); // must get more // no duplicate data transfers relay = false; } else { kDebug(7116) <<"imapParser::parseLiteral - error parsing {} -" /*<< strLen*/; } } else { inWords.clear(); kDebug(7116) <<"imapParser::parseLiteral - error parsing unmatched {"; } skipWS (inWords); return retVal; } return parseOneWord(inWords, stopAtBracket); } // does not know about literals ( {7} literal ) QByteArray imapParser::parseOneWord (parseString & inWords, bool stopAtBracket) { uint len = inWords.length(); if (len == 0) { return QByteArray(); } if (len > 0 && inWords[0] == '"') { unsigned int i = 1; bool quote = false; while (i < len && (inWords[i] != '"' || quote)) { if (inWords[i] == '\\') quote = !quote; else quote = false; i++; } if (i < len) { QByteArray retVal; retVal.resize(i); inWords.pos++; inWords.takeLeftNoResize(retVal, i - 1); len = i - 1; int offset = 0; for (unsigned int j = 0; j < len; j++) { if (retVal[j] == '\\') { offset++; j++; } retVal[j - offset] = retVal[j]; } retVal.resize( len - offset ); inWords.pos += i; skipWS (inWords); return retVal; } else { kDebug(7116) <<"imapParser::parseOneWord - error parsing unmatched \""; QByteArray retVal = inWords.cstr(); inWords.clear(); return retVal; } } else { // not quoted unsigned int i; // search for end for (i = 0; i < len; ++i) { char ch = inWords[i]; if (ch <= ' ' || ch == '(' || ch == ')' || (stopAtBracket && (ch == '[' || ch == ']'))) break; } QByteArray retVal; retVal.resize(i); inWords.takeLeftNoResize(retVal, i); inWords.pos += i; if (retVal == "NIL") { retVal.truncate(0); } skipWS (inWords); return retVal; } } bool imapParser::parseOneNumber (parseString & inWords, ulong & num) { bool valid; num = parseOneWord(inWords, true).toULong(&valid); return valid; } bool imapParser::hasCapability (const QString & cap) { QString c = cap.toLower(); // kDebug(7116) <<"imapParser::hasCapability - Looking for '" << cap <<"'"; for (QStringList::ConstIterator it = imapCapabilities.constBegin (); it != imapCapabilities.constEnd (); ++it) { // kDebug(7116) <<"imapParser::hasCapability - Examining '" << (*it) <<"'"; if ( !(kasciistricmp(c.toAscii(), (*it).toAscii())) ) { return true; } } return false; } void imapParser::removeCapability (const QString & cap) { imapCapabilities.removeAll(cap.toLower()); } QString imapParser::namespaceForBox( const QString & box ) { kDebug(7116) <<"imapParse::namespaceForBox" << box; QString myNamespace; if ( !box.isEmpty() ) { const QList list = namespaceToDelimiter.keys(); QString cleanPrefix; for ( QList::ConstIterator it = list.begin(); it != list.end(); ++it ) { if ( !(*it).isEmpty() && box.contains( *it ) ) return (*it); } } return myNamespace; } diff --git a/kioslave/imap4/mimeheader.cpp b/kioslave/imap4/mimeheader.cpp index 28547f6c8..3c8917c5f 100644 --- a/kioslave/imap4/mimeheader.cpp +++ b/kioslave/imap4/mimeheader.cpp @@ -1,726 +1,726 @@ /*************************************************************************** mimeheader.cc - description ------------------- begin : Fri Oct 20 2000 copyright : (C) 2000 by Sven Carstens email : s.carstens@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mimeheader.h" #include "mimehdrline.h" #include "mailheader.h" #include // #include #include #include #include #include #include #include #include using namespace KIMAP; mimeHeader::mimeHeader () : typeList (), dispositionList (), _contentType("application/octet-stream"), _contentDisposition(), _contentDescription() { // Case insensitive hashes are killing us. Also are they too small? nestedMessage = NULL; contentLength = 0; } mimeHeader::~mimeHeader () { } /* QPtrList mimeHeader::getAllParts() { QPtrList retVal; // caller is responsible for clearing retVal.setAutoDelete( false ); nestedParts.setAutoDelete( false ); // shallow copy retVal = nestedParts; // can't have duplicate pointers nestedParts.clear(); // restore initial state nestedParts.setAutoDelete( true ); return retVal; } */ void mimeHeader::addHdrLine (mimeHdrLine * aHdrLine) { mimeHdrLine *addLine = new mimeHdrLine (aHdrLine); if (addLine) { originalHdrLines.append (addLine); if (qstrnicmp (addLine->getLabel (), "Content-", 8)) { additionalHdrLines.append (addLine); } else { int skip; const char *aCStr = addLine->getValue ().data (); QHash < QString, QString > *aList = 0; skip = mimeHdrLine::parseSeparator (';', aCStr); if (skip > 0) { int cut = 0; if (skip >= 2) { if (aCStr[skip - 1] == '\r') cut++; if (aCStr[skip - 1] == '\n') cut++; if (aCStr[skip - 2] == '\r') cut++; if (aCStr[skip - 1] == ';') cut++; } QByteArray mimeValue(aCStr, skip - cut); if (!qstricmp (addLine->getLabel (), "Content-Disposition")) { aList = &dispositionList; setDisposition( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Type")) { aList = &typeList; setType( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Transfer-Encoding")) { setEncoding( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-ID")) { setID( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Description")) { setDescription( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-MD5")) { setMD5( mimeValue ); } else if (!qstricmp (addLine->getLabel (), "Content-Length")) { contentLength = mimeValue.toUInt (); } else { additionalHdrLines.append (addLine); } // cout << addLine->getLabel().data() << ": '" << mimeValue.data() << "'" << endl; aCStr += skip; while ((skip = mimeHdrLine::parseSeparator (';', aCStr))) { if (skip > 0) { if (aList) addParameter (QByteArray (aCStr, skip).simplified(), *aList); mimeValue = QByteArray (addLine->getValue ().data (), skip); aCStr += skip; } else break; } } } } } void mimeHeader::addParameter (const QByteArray& aParameter, QHash < QString, QString > &aList) { QString aValue; QByteArray aLabel; int pos = aParameter.indexOf ('='); // cout << aParameter.left(pos).data(); aValue = QString::fromLatin1 (aParameter.right (aParameter.length () - pos - 1)); aLabel = aParameter.left (pos); if (aValue[0] == '"') aValue = aValue.mid (1, aValue.length () - 2); aList.insert (aLabel.toLower(), aValue); // cout << "=" << aValue->data() << endl; } QString mimeHeader::getDispositionParm (const QByteArray& aStr) { return getParameter (aStr, dispositionList); } QString mimeHeader::getTypeParm (const QByteArray& aStr) { return getParameter (aStr, typeList); } void mimeHeader::setDispositionParm (const QByteArray& aLabel, const QString& aValue) { setParameter (aLabel, aValue, dispositionList); return; } void mimeHeader::setTypeParm (const QByteArray& aLabel, const QString& aValue) { setParameter (aLabel, aValue, typeList); } QHashIterator < QString, QString > mimeHeader::getDispositionIterator () { return QHashIterator < QString, QString > (dispositionList); } QHashIterator < QString, QString > mimeHeader::getTypeIterator () { return QHashIterator < QString, QString > (typeList); } QListIterator < mimeHdrLine *> mimeHeader::getOriginalIterator () { return QListIterator < mimeHdrLine *> (originalHdrLines); } QListIterator < mimeHdrLine *> mimeHeader::getAdditionalIterator () { return QListIterator < mimeHdrLine *> (additionalHdrLines); } void mimeHeader::outputHeader (mimeIO & useIO) { if (!getDisposition ().isEmpty ()) { useIO.outputMimeLine (QByteArray ("Content-Disposition: ") + getDisposition () + outputParameter (dispositionList)); } if (!getType ().isEmpty ()) { useIO.outputMimeLine (QByteArray ("Content-Type: ") + getType () + outputParameter (typeList)); } if (!getDescription ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-Description: ") + getDescription ()); if (!getID ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-ID: ") + getID ()); if (!getMD5 ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-MD5: ") + getMD5 ()); if (!getEncoding ().isEmpty ()) useIO.outputMimeLine (QByteArray ("Content-Transfer-Encoding: ") + getEncoding ()); QListIterator < mimeHdrLine *> ait = getAdditionalIterator (); mimeHdrLine *hdrline; while (ait.hasNext ()) { hdrline = ait.next(); useIO.outputMimeLine (hdrline->getLabel () + ": " + hdrline->getValue ()); } useIO.outputMimeLine (QByteArray ("")); } QString mimeHeader::getParameter (const QByteArray& aStr, QHash < QString, QString > &aDict) { QString retVal, found; //see if it is a normal parameter found = aDict.value ( aStr ); if ( found.isEmpty() ) { //might be a continuated or encoded parameter found = aDict.value ( aStr + '*' ); if ( found.isEmpty() ) { //continuated parameter QString decoded, encoded; int part = 0; do { QByteArray search; search.setNum (part); search = aStr + '*' + search; found = aDict.value (search); if ( found.isEmpty() ) { found = aDict.value (search + '*'); if ( !found.isEmpty() ) encoded += KIMAP::encodeRFC2231String (found); } else { encoded += found; } part++; } while ( !found.isEmpty() ); if (encoded.contains ('\'')) { retVal = KIMAP::decodeRFC2231String (encoded.toLocal8Bit ()); } else { retVal = KIMAP::decodeRFC2231String (QByteArray ("''") + encoded.toLocal8Bit ()); } } else { //simple encoded parameter retVal = KIMAP::decodeRFC2231String (found.toLocal8Bit ()); } } else { retVal = found; } return retVal; } void mimeHeader::setParameter (const QByteArray& aLabel, const QString& aValue, QHash < QString, QString > &aDict) { bool encoded = true; uint vlen, llen; QString val = aValue; //see if it needs to get encoded if (encoded && !aLabel.contains('*')) { val = KIMAP::encodeRFC2231String (aValue); } //kDebug(7116) <<"mimeHeader::setParameter() - val = '" << val <<"'"; //see if it needs to be truncated vlen = val.length(); llen = aLabel.length(); if (vlen + llen + 4 > 80 && llen < 80 - 8 - 2 ) { const int limit = 80 - 8 - 2 - (int)llen; // the -2 is there to allow extending the length of a part of val // by 1 or 2 in order to prevent an encoded character from being // split in half int i = 0; QString shortValue; QByteArray shortLabel; while (!val.isEmpty ()) { int partLen; // the length of the next part of the value if ( limit >= int(vlen) ) { // the rest of the value fits completely into one continued header partLen = vlen; } else { partLen = limit; // make sure that we don't split an encoded char in half if ( val[partLen-1] == '%' ) { partLen += 2; } else if ( partLen > 1 && val[partLen-2] == '%' ) { partLen += 1; } // make sure partLen does not exceed vlen (could happen in case of // an incomplete encoded char) if ( partLen > int(vlen) ) { partLen = vlen; } } shortValue = val.left( partLen ); shortLabel.setNum (i); shortLabel = aLabel + '*' + shortLabel; val = val.right( vlen - partLen ); vlen = vlen - partLen; if (encoded) { if (i == 0) { shortValue = "''" + shortValue; } shortLabel += '*'; } //kDebug(7116) <<"mimeHeader::setParameter() - shortLabel = '" << shortLabel <<"'"; //kDebug(7116) <<"mimeHeader::setParameter() - shortValue = '" << shortValue <<"'"; //kDebug(7116) <<"mimeHeader::setParameter() - val = '" << val <<"'"; aDict.insert (shortLabel.toLower(), shortValue); i++; } } else { aDict.insert (aLabel.toLower(), val); } } QByteArray mimeHeader::outputParameter (QHash < QString, QString > &aDict) { QByteArray retVal; QHashIterator < QString, QString > it (aDict); while (it.hasNext ()) { it.next(); retVal += (";\n\t" + it.key() + '=').toLatin1 (); if (it.value().indexOf (' ') > 0 || it.value().indexOf (';') > 0) { retVal += '"' + it.value().toUtf8 () + '"'; } else { retVal += it.value().toUtf8 (); } } retVal += '\n'; return retVal; } void mimeHeader::outputPart (mimeIO & useIO) { QListIterator < mimeHeader *> nestedParts = getNestedIterator (); QByteArray boundary; if (!getTypeParm ("boundary").isEmpty ()) boundary = getTypeParm ("boundary").toLatin1 (); outputHeader (useIO); if (!getPreBody ().isEmpty ()) useIO.outputMimeLine (getPreBody ()); if (getNestedMessage ()) getNestedMessage ()->outputPart (useIO); mimeHeader *mimeline; while (nestedParts.hasNext()) { mimeline = nestedParts.next(); if (!boundary.isEmpty ()) useIO.outputMimeLine ("--" + boundary); mimeline->outputPart (useIO); } if (!boundary.isEmpty ()) useIO.outputMimeLine ("--" + boundary + "--"); if (!getPostBody ().isEmpty ()) useIO.outputMimeLine (getPostBody ()); } #if 0 int mimeHeader::parsePart (mimeIO & useIO, const QString& boundary) { int retVal = 0; bool mbox = false; QByteArray preNested, postNested; mbox = parseHeader (useIO); kDebug(7116) <<"mimeHeader::parsePart - parsing part '" << getType () <<"'"; if (!qstrnicmp (getType (), "Multipart", 9)) { retVal = parseBody (useIO, preNested, getTypeParm ("boundary")); //this is a message in mime format stuff setPreBody (preNested); int localRetVal; do { mimeHeader *aHeader = new mimeHeader; // set default type for multipart/digest if (!qstrnicmp (getType (), "Multipart/Digest", 16)) aHeader->setType ("Message/RFC822"); localRetVal = aHeader->parsePart (useIO, getTypeParm ("boundary")); addNestedPart (aHeader); } while (localRetVal); //get nested stuff } if (!qstrnicmp (getType (), "Message/RFC822", 14)) { mailHeader *msgHeader = new mailHeader; retVal = msgHeader->parsePart (useIO, boundary); setNestedMessage (msgHeader); } else { retVal = parseBody (useIO, postNested, boundary, mbox); //just a simple part remaining setPostBody (postNested); } return retVal; } int mimeHeader::parseBody (mimeIO & useIO, QByteArray & messageBody, const QString& boundary, bool mbox) { QByteArray inputStr; QByteArray buffer; QString partBoundary; QString partEnd; int retVal = 0; //default is last part if (!boundary.isEmpty ()) { partBoundary = QString ("--") + boundary; partEnd = QString ("--") + boundary + "--"; } while (useIO.inputLine (inputStr)) { //check for the end of all parts if (!partEnd.isEmpty () && !qstrnicmp (inputStr, partEnd.toLatin1 (), partEnd.length () - 1)) { retVal = 0; //end of these parts break; } else if (!partBoundary.isEmpty () && !qstrnicmp (inputStr, partBoundary.toLatin1 (), partBoundary.length () - 1)) { retVal = 1; //continue with next part break; } else if (mbox && inputStr.startsWith ("From ") ) { retVal = 0; // end of mbox break; } buffer += inputStr; if (buffer.length () > 16384) { messageBody += buffer; buffer = ""; } } messageBody += buffer; return retVal; } #endif bool mimeHeader::parseHeader (mimeIO & useIO) { bool mbox = false; bool first = true; mimeHdrLine my_line; QByteArray inputStr; kDebug(7116) <<"mimeHeader::parseHeader - starting parsing"; while (useIO.inputLine (inputStr)) { int appended; - if (!inputStr.startsWith ("From ") || !first) + if (!inputStr.startsWith("From ") || !first) //krazy:exclude=strings { first = false; appended = my_line.appendStr (inputStr); if (!appended) { addHdrLine (&my_line); appended = my_line.setStr (inputStr); } if (appended <= 0) break; } else { mbox = true; first = false; } inputStr = QByteArray(); } kDebug(7116) <<"mimeHeader::parseHeader - finished parsing"; return mbox; } mimeHeader * mimeHeader::bodyPart (const QString & _str) { // see if it is nested a little deeper int pt = _str.indexOf('.'); if (pt != -1) { QString tempStr = _str; mimeHeader *tempPart; tempStr = _str.right (_str.length () - pt - 1); if (nestedMessage) { kDebug(7116) <<"mimeHeader::bodyPart - recursing message"; tempPart = nestedMessage->nestedParts.at (_str.left(pt).toULong() - 1); } else { kDebug(7116) <<"mimeHeader::bodyPart - recursing mixed"; tempPart = nestedParts.at (_str.left(pt).toULong() - 1); } if (tempPart) tempPart = tempPart->bodyPart (tempStr); return tempPart; } kDebug(7116) <<"mimeHeader::bodyPart - returning part" << _str; // or pick just the plain part if (nestedMessage) { kDebug(7116) <<"mimeHeader::bodyPart - message"; return nestedMessage->nestedParts.at (_str.toULong () - 1); } kDebug(7116) <<"mimeHeader::bodyPart - mixed"; return nestedParts.at (_str.toULong () - 1); } void mimeHeader::serialize(QDataStream& stream) { int nestedcount = nestedParts.count(); if (nestedParts.isEmpty() && nestedMessage) nestedcount = 1; stream << nestedcount; stream << _contentType; stream << QString (getTypeParm ("name")); stream << _contentDescription; stream << _contentDisposition; stream << _contentEncoding; stream << contentLength; stream << partSpecifier; // serialize nested message if (nestedMessage) nestedMessage->serialize(stream); // serialize nested parts if (!nestedParts.isEmpty()) { QListIterator < mimeHeader *> it(nestedParts); mimeHeader* part; while ( it.hasNext() ) { part = it.next(); part->serialize( stream ); } } } #ifdef KMAIL_COMPATIBLE // compatibility subroutines QString mimeHeader::bodyDecoded () { kDebug(7116) <<"mimeHeader::bodyDecoded"; QByteArray temp = bodyDecodedBinary (); return QString::fromLatin1 (temp.data (), temp.count ()); } QByteArray mimeHeader::bodyDecodedBinary () { QByteArray retVal; - if (contentEncoding.startsWith ("quoted-printable", Qt::CaseInsensitive) ) + if (contentEncoding.startsWith (QLatin1String("quoted-printable"), Qt::CaseInsensitive) ) retVal = KCodecs::quotedPrintableDecode(postMultipartBody); - else if (contentEncoding.startsWith ("base64", Qt::CaseInsensitive) ) + else if (contentEncoding.startsWith (QLatin1String("base64"), Qt::CaseInsensitive) ) KCodecs::base64Decode(postMultipartBody, retVal); else retVal = postMultipartBody; - kDebug(7116) <<"mimeHeader::bodyDecodedBinary - size is" << retVal.size (); + kDebug(7116) << "mimeHeader::bodyDecodedBinary - size is" << retVal.size (); return retVal; } void mimeHeader::setBodyEncodedBinary (const QByteArray & _arr) { setBodyEncoded (_arr); } void mimeHeader::setBodyEncoded (const QByteArray & _arr) { QByteArray setVal; kDebug(7116) <<"mimeHeader::setBodyEncoded - in size" << _arr.size (); - if (contentEncoding.startsWith ("quoted-printable", Qt::CaseInsensitive) ) + if (contentEncoding.startsWith (QLatin1String("quoted-printable"), Qt::CaseInsensitive) ) setVal = KCodecs::quotedPrintableEncode(_arr); - else if (contentEncoding.startsWith ("base64", Qt::CaseInsensitive) ) + else if (contentEncoding.startsWith (QLatin1String("base64"), Qt::CaseInsensitive) ) KCodecs::base64Encode(_arr, setVal); else setVal.duplicate (_arr); kDebug(7116) <<"mimeHeader::setBodyEncoded - out size" << setVal.size (); postMultipartBody.duplicate (setVal); kDebug(7116) <<"mimeHeader::setBodyEncoded - out size" << postMultipartBody.size (); } QString mimeHeader::iconName () { QString fileName = KMimeType::mimeType (contentType.toLower ())->icon (QString(), false); QString iconFileName = KGlobal::mainComponent().iconLoader ()->iconPath (fileName, KIconLoader::Desktop); // if (iconFileName.isEmpty()) // iconFileName = KGlobal::mainComponent().iconLoader()->iconPath( "unknown", KIconLoader::Desktop ); return iconFileName; } void mimeHeader::setNestedMessage (mailHeader * inPart, bool destroy) { // if(nestedMessage && destroy) delete nestedMessage; nestedMessage = inPart; } QString mimeHeader::headerAsString () { mimeIOQString myIO; outputHeader (myIO); return myIO.getString (); } QString mimeHeader::magicSetType (bool aAutoDecode) { QByteArray body; if (aAutoDecode) body = bodyDecodedBinary (); else body = postMultipartBody; KMimeType::Ptr mime = KMimeType::findByContent (body); QString mimetype = mime->name(); contentType = mimetype; return mimetype; } #endif diff --git a/kioslave/imap4/mimeio.cpp b/kioslave/imap4/mimeio.cpp index 8352ec319..fa3537894 100644 --- a/kioslave/imap4/mimeio.cpp +++ b/kioslave/imap4/mimeio.cpp @@ -1,177 +1,179 @@ /*************************************************************************** mimeio.cc - description ------------------- begin : Wed Oct 25 2000 copyright : (C) 2000 by Sven Carstens email : s.carstens@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mimeio.h" #include using namespace std; mimeIO::mimeIO () { theCRLF = "\r\n"; crlfLen = 2; } mimeIO::~mimeIO () { } int mimeIO::inputLine (QByteArray & aLine) { char input; aLine = QByteArray(); while (inputChar (input)) { aLine += input; if (input == '\n') break; } // cout << aLine.length() << " - " << aLine; return aLine.length (); } int mimeIO::outputLine (const QByteArray & aLine, int len) { int i; if (len == -1) { len = aLine.length(); } int start = len; - for (i = 0; i < start; i++) - if (!outputChar (aLine[i])) + for (i = 0; i < start; ++i) { + if (!outputChar (aLine[i])) { break; + } + } return i; } int mimeIO::outputMimeLine (const QByteArray & inLine) { int retVal = 0; QByteArray aLine = inLine; int len = inLine.length(); int theLF = aLine.lastIndexOf ('\n'); if (theLF == len - 1 && theLF != -1) { //we have a trailing LF, now check for CR if (aLine[theLF - 1] == '\r') theLF--; //truncate the line aLine.truncate(theLF); len = theLF; theLF = -1; } //now truncate the line { int start, end, offset; start = 0; end = aLine.indexOf ('\n', start); while (end >= 0) { offset = 1; if (end && aLine[end - 1] == '\r') { offset++; end--; } outputLine (aLine.mid (start, end - start) + theCRLF, end - start + crlfLen); start = end + offset; end = aLine.indexOf ('\n', start); } outputLine (aLine.mid (start, len - start) + theCRLF, len - start + crlfLen); } return retVal; } int mimeIO::inputChar (char &aChar) { if (cin.eof ()) { // cout << "EOF" << endl; return 0; } cin.get (aChar); return 1; } int mimeIO::outputChar (char aChar) { cout << aChar; return 1; } // void mimeIO::setCRLF (const char *aCRLF) // { // theCRLF = aCRLF; // crlfLen = strlen(aCRLF); // } mimeIOQFile::mimeIOQFile (const QString & aName): mimeIO (), myFile (aName) { myFile.open (QIODevice::ReadOnly); } mimeIOQFile::~mimeIOQFile () { myFile.close (); } int mimeIOQFile::outputLine (const QByteArray &, int) { return 0; } int mimeIOQFile::inputLine (QByteArray & data) { data.resize( 1024 ); myFile.readLine (data.data(), 1024); return data.length (); } mimeIOQString::mimeIOQString () { } mimeIOQString::~mimeIOQString () { } int mimeIOQString::outputLine (const QByteArray & _str, int len) { if (len == -1) { len = _str.length(); } theString += _str; return len; } int mimeIOQString::inputLine (QByteArray & _str) { if (theString.isEmpty ()) return 0; int i = theString.indexOf ('\n'); if (i == -1) return 0; _str = theString.left (i + 1).toLatin1 (); theString = theString.right (theString.length () - i - 1); return _str.length (); } diff --git a/kioslave/nntp/nntp.cpp b/kioslave/nntp/nntp.cpp index 3269f7beb..1ccf8fa24 100644 --- a/kioslave/nntp/nntp.cpp +++ b/kioslave/nntp/nntp.cpp @@ -1,964 +1,964 @@ /* 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" ) ); + 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( "full" ) ) + 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 ) ) { 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( "\r\n" ) ) + 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( "\r\n" ) ) + 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; switch ( res_code ) { case 480: errCode = ERR_COULD_NOT_LOGIN; break; default: errCode = ERR_INTERNAL; } 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 9fa28d174..46849c866 100644 --- a/kioslave/nntp/nntp.h +++ b/kioslave/nntp/nntp.h @@ -1,141 +1,141 @@ /* 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 */ 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 (successfull authentication) otherwise. + * are available; 281 (successful authentication) otherwise. */ int authenticate(); }; #endif diff --git a/kioslave/sieve/sieve-config.h.cmake b/kioslave/sieve/sieve-config.h.cmake index 115638c37..e11fa9f4a 100644 --- a/kioslave/sieve/sieve-config.h.cmake +++ b/kioslave/sieve/sieve-config.h.cmake @@ -1,6 +1,2 @@ -/* Define if the sieve KIO::Slave must be able to talk to timsieved <= 1.1.0 - */ -/* #undef HAVE_BROKEN_TIMSIEVED */ - /* Define if you have cyrus-sasl2 libraries */ #cmakedefine HAVE_LIBSASL2 1 diff --git a/kioslave/sieve/sieve.cpp b/kioslave/sieve/sieve.cpp index 4b58be149..41320f997 100644 --- a/kioslave/sieve/sieve.cpp +++ b/kioslave/sieve/sieve.cpp @@ -1,1283 +1,1273 @@ /*************************************************************************** sieve.cpp - description ------------------- begin : Thu Dec 20 18:47:08 EST 2001 copyright : (C) 2001 by Hamish Rodda email : meddie@yoyo.cc.monash.edu.au ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ /** * Portions adapted from the SMTP ioslave. * Copyright (c) 2000, 2001 Alex Zepeda * Copyright (c) 2001 Michael Häckel * All rights reserved. * * Policy: the function where the error occurs calls error(). A result of * false, where it signifies an error, thus doesn't need to call error() itself. */ #include "sieve.h" #include "common.h" extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include static const int debugArea = 7122; #define ksDebug kDebug( debugArea ) #define SIEVE_DEFAULT_PORT 2000 static sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, NULL, NULL }, { SASL_CB_NOECHOPROMPT, NULL, NULL }, { SASL_CB_GETREALM, NULL, NULL }, { SASL_CB_USER, NULL, NULL }, { SASL_CB_AUTHNAME, NULL, NULL }, { SASL_CB_PASS, NULL, NULL }, { SASL_CB_CANON_USER, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; static const unsigned int SIEVE_DEFAULT_RECIEVE_BUFFER = 512; using namespace KIO; extern "C" { KDE_EXPORT int kdemain(int argc, char **argv) { KComponentData instance("kio_sieve" ); ksDebug << "*** Starting kio_sieve " << endl; if (argc != 4) { ksDebug << "Usage: kio_sieve protocol domain-socket1 domain-socket2" << endl; return -1; } if (!initSASL()) ::exit(-1); kio_sieveProtocol slave(argv[2], argv[3]); slave.dispatchLoop(); sasl_done(); ksDebug << "*** kio_sieve Done" << endl; return 0; } } /* ---------------------------------------------------------------------------------- */ kio_sieveResponse::kio_sieveResponse() { clear(); } /* ---------------------------------------------------------------------------------- */ const uint& kio_sieveResponse::getType() const { return rType; } /* ---------------------------------------------------------------------------------- */ uint kio_sieveResponse::getQuantity() const { return quantity; } /* ---------------------------------------------------------------------------------- */ const QByteArray& kio_sieveResponse::getAction() const { return key; } /* ---------------------------------------------------------------------------------- */ const QByteArray& kio_sieveResponse::getKey() const { return key; } /* ---------------------------------------------------------------------------------- */ const QByteArray& kio_sieveResponse::getVal() const { return val; } /* ---------------------------------------------------------------------------------- */ const QByteArray& kio_sieveResponse::getExtra() const { return extra; } /* ---------------------------------------------------------------------------------- */ void kio_sieveResponse::setQuantity(const uint& newQty) { rType = QUANTITY; quantity = newQty; } /* ---------------------------------------------------------------------------------- */ void kio_sieveResponse::setAction(const QByteArray& newAction) { rType = ACTION; key = newAction; } /* ---------------------------------------------------------------------------------- */ void kio_sieveResponse::setKey(const QByteArray& newKey) { rType = KEY_VAL_PAIR; key = newKey; } /* ---------------------------------------------------------------------------------- */ void kio_sieveResponse::setVal(const QByteArray& newVal) { val = newVal; } /* ---------------------------------------------------------------------------------- */ void kio_sieveResponse::setExtra(const QByteArray& newExtra) { extra = newExtra; } /* ---------------------------------------------------------------------------------- */ void kio_sieveResponse::clear() { rType = NONE; extra = key = val = QByteArray(); quantity = 0; } /* ---------------------------------------------------------------------------------- */ kio_sieveProtocol::kio_sieveProtocol(const QByteArray &pool_socket, const QByteArray &app_socket) : TCPSlaveBase("sieve", pool_socket, app_socket, false) , m_connMode(NORMAL) , m_supportsTLS(false) , m_shouldBeConnected(false) , m_allowUnencrypted(false) { } /* ---------------------------------------------------------------------------------- */ kio_sieveProtocol::~kio_sieveProtocol() { if ( isConnected() ) disconnect(); } /* ---------------------------------------------------------------------------------- */ void kio_sieveProtocol::setHost (const QString &host, quint16 port, const QString &user, const QString &pass) { if ( isConnected() && ( m_sServer != host || m_port != port || m_sUser != user || m_sPass != pass ) ) { disconnect(); } m_sServer = host; m_port = port ? port : SIEVE_DEFAULT_PORT; m_sUser = user; m_sPass = pass; m_supportsTLS = false; } /* ---------------------------------------------------------------------------------- */ void kio_sieveProtocol::openConnection() { m_connMode = CONNECTION_ORIENTED; connect(); } bool kio_sieveProtocol::parseCapabilities(bool requestCapabilities/* = false*/) { ksDebug << endl; // Setup... bool ret = false; if (requestCapabilities) { sendData("CAPABILITY"); } while (receiveData()) { ksDebug << "Looping receive" << endl; if (r.getType() == kio_sieveResponse::ACTION) { if ( r.getAction().toLower().contains("ok") ) { ksDebug << "Sieve server ready & awaiting authentication." << endl; break; } else ksDebug << "Unknown action " << r.getAction() << "." << endl; } else if (r.getKey() == "IMPLEMENTATION") { ksDebug << "Connected to Sieve server: " << r.getVal() << endl; ret = true; setMetaData("implementation", r.getVal()); m_implementation = r.getVal(); } else if (r.getKey() == "SASL") { // Save list of available SASL methods m_sasl_caps = QString(r.getVal()).split(' '); ksDebug << "Server SASL authentication methods: " << m_sasl_caps.join(", ") << endl; setMetaData("saslMethods", r.getVal()); } else if (r.getKey() == "SIEVE") { // Save script capabilities; report back as meta data: ksDebug << "Server script capabilities: " << QString(r.getVal()).split(' ').join(", ") << endl; setMetaData("sieveExtensions", r.getVal()); } else if (r.getKey() == "STARTTLS") { // The server supports TLS ksDebug << "Server supports TLS" << endl; m_supportsTLS = true; setMetaData("tlsSupported", "true"); } else { ksDebug << "Unrecognised key " << r.getKey() << endl; } } if (!m_supportsTLS) { setMetaData("tlsSupported", "false"); } return ret; } /* ---------------------------------------------------------------------------------- */ /** * Checks if connection parameters have changed. * If it it, close the current connection */ void kio_sieveProtocol::changeCheck( const KUrl &url ) { QString auth; // Check the SASL auth mechanism in the 'sasl' metadata... if (!metaData("sasl").isEmpty()) auth = metaData("sasl").toUpper(); else { // ... and if not found, check the x-mech=AUTH query part of the url. QString query = url.query(); if ( query.startsWith('?') ) query.remove( 0, 1 ); QStringList q = query.split( ',' ); QStringList::iterator it; for ( it = q.begin(); it != q.end(); ++it ) { if ( ( (*it).section('=',0,0) ).toLower() == "x-mech" ) { auth = ( (*it).section('=',1) ).toUpper(); break; } } } ksDebug << "auth: " << auth << " m_sAuth: " << m_sAuth << endl; if ( m_sAuth != auth ) { m_sAuth = auth; if ( isConnected() ) disconnect(); } // For TLS, only disconnect if we are unencrypted and are // no longer allowed (otherwise, it's still fine): const bool allowUnencryptedNow = url.queryItem("x-allow-unencrypted") == "true" ; if ( m_allowUnencrypted && !allowUnencryptedNow ) if ( isConnected() ) disconnect(); m_allowUnencrypted = allowUnencryptedNow; } /* ---------------------------------------------------------------------------------- */ /** * Connects to the server. * returns false and calls error() if an error occurred. */ bool kio_sieveProtocol::connect(bool useTLSIfAvailable) { ksDebug << endl; if (isConnected()) return true; infoMessage(i18n("Connecting to %1...", m_sServer)); if (m_connMode == CONNECTION_ORIENTED && m_shouldBeConnected) { error(ERR_CONNECTION_BROKEN, i18n("The connection to the server was lost.")); return false; } setBlocking(true); if (!connectToHost(QLatin1String("sieve"), m_sServer, m_port)) { return false; } if (!parseCapabilities()) { disconnectFromHost(); error(ERR_UNSUPPORTED_PROTOCOL, i18n("Server identification failed.")); return false; } // Attempt to start TLS if ( !m_allowUnencrypted && !QSslSocket::supportsSsl() ) { error( ERR_SLAVE_DEFINED, i18n("Can not use TLS since the underlying Qt library does not support it.") ); disconnect(); return false; } if ( !m_allowUnencrypted && useTLSIfAvailable && QSslSocket::supportsSsl() && !m_supportsTLS && messageBox( WarningContinueCancel, i18n("TLS encryption was requested, but your Sieve server does not advertise TLS in its capabilities.\n" "You can choose to try to initiate TLS negotiations nonetheless, or cancel the operation."), i18n("Server Does Not Advertise TLS"), i18n("&Start TLS nonetheless"), i18n("&Cancel") ) != KMessageBox::Continue ) { error( ERR_USER_CANCELED, i18n("TLS encryption requested, but not supported by server.") ); disconnect(); return false; } // FIXME find a test server and test that this works // TODO ask the system whether SSL is available if (useTLSIfAvailable && QSslSocket::supportsSsl()) { sendData("STARTTLS"); if (operationSuccessful()) { ksDebug << "TLS has been accepted. Starting TLS..." << endl << "WARNING this is untested and may fail."; if (startSsl()) { ksDebug << "TLS enabled successfully." << endl; // reparse capabilities: parseCapabilities( requestCapabilitiesAfterStartTLS() ); } else { ksDebug << "TLS initiation failed."; if ( m_allowUnencrypted ) { disconnect(true); return connect(false); } messageBox( Information, i18n("Your Sieve server claims to support TLS, " "but negotiation was unsuccessful."), i18n("Connection Failed") ); disconnect(true); return false; } } else if ( !m_allowUnencrypted ) { ksDebug << "Server incapable of TLS."; disconnect(); error( ERR_SLAVE_DEFINED, i18n("The server does not seem to support TLS. " "Disable TLS if you want to connect without encryption.") ); return false; } else ksDebug << "Server incapable of TLS. Transmitted documents will be unencrypted." << endl; } else ksDebug << "We are incapable of TLS. Transmitted documents will be unencrypted." << endl; assert( m_allowUnencrypted || isUsingSsl() ); infoMessage(i18n("Authenticating user...")); if (!authenticate()) { disconnect(); error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed.")); return false; } m_shouldBeConnected = true; return true; } /* ---------------------------------------------------------------------------------- */ void kio_sieveProtocol::closeConnection() { m_connMode = CONNECTION_ORIENTED; disconnect(); } /* ---------------------------------------------------------------------------------- */ void kio_sieveProtocol::disconnect(bool forcibly) { if (!forcibly) { sendData("LOGOUT"); - if (!operationSuccessful()) + if (!operationSuccessful()) { ksDebug << "Server did not logout cleanly." << endl; + } } disconnectFromHost(); m_shouldBeConnected = false; } /* ---------------------------------------------------------------------------------- */ /*void kio_sieveProtocol::slave_status() { slaveStatus(isConnected() ? m_sServer : "", isConnected()); finished(); }*/ /* ---------------------------------------------------------------------------------- */ void kio_sieveProtocol::special(const QByteArray &data) { int tmp; QDataStream stream( data ); KUrl url; stream >> tmp; switch (tmp) { case 1: stream >> url; if (!activate(url)) return; break; case 2: if (!deactivate()) return; break; case 3: parseCapabilities(true); break; } infoMessage(i18nc("special command completed", "Done.")); finished(); } /* ---------------------------------------------------------------------------------- */ bool kio_sieveProtocol::activate(const KUrl& url) { changeCheck( url ); if (!connect()) return false; infoMessage(i18n("Activating script...")); QString filename = url.fileName( KUrl::ObeyTrailingSlash ); if (filename.isEmpty()) { error(ERR_DOES_NOT_EXIST, url.prettyUrl()); return false; } if (!sendData("SETACTIVE \"" + filename.toUtf8() + "\"")) return false; if (operationSuccessful()) { ksDebug << "Script activation complete." << endl; return true; } else { error(ERR_INTERNAL_SERVER, i18n("There was an error activating the script.")); return false; } } /* ---------------------------------------------------------------------------------- */ bool kio_sieveProtocol::deactivate() { if (!connect()) return false; if (!sendData("SETACTIVE \"\"")) return false; if (operationSuccessful()) { ksDebug << "Script deactivation complete." << endl; return true; } else { error(ERR_INTERNAL_SERVER, i18n("There was an error deactivating the script.")); return false; } } static void append_lf2crlf( QByteArray & out, const QByteArray & in ) { if ( in.isEmpty() ) return; const unsigned int oldOutSize = out.size(); out.resize( oldOutSize + 2 * in.size() ); const char * s = in.begin(); const char * const end = in.end(); char * d = out.begin() + oldOutSize; char last = '\0'; while ( s < end ) { if ( *s == '\n' && last != '\r' ) *d++ = '\r'; *d++ = last = *s++; } out.resize( d - out.begin() ); } void kio_sieveProtocol::put(const KUrl& url, int /*permissions*/, KIO::JobFlags) { changeCheck( url ); if (!connect()) return; infoMessage(i18n("Sending data...")); QString filename = url.fileName( KUrl::ObeyTrailingSlash ); if (filename.isEmpty()) { error(ERR_MALFORMED_URL, url.prettyUrl()); return; } QByteArray data; for (;;) { dataReq(); QByteArray buffer; const int newSize = readData(buffer); append_lf2crlf( data, buffer ); if ( newSize < 0 ) { // read error: network in unknown state so disconnect error(ERR_COULD_NOT_READ, i18n("KIO data supply error.")); return; } if ( newSize == 0 ) break; } // script size int bufLen = (int)data.size(); totalSize(bufLen); // timsieved 1.1.0: // C: HAVESPACE "rejected" 74 // S: NO "Number expected" // C: HAVESPACE 74 // S: NO "Missing script name" // S: HAVESPACE "rejected" "74" // C: NO "Number expected" // => broken, we can't use it :-( // (will be fixed in Cyrus 2.1.10) -#ifndef HAVE_BROKEN_TIMSIEVED - // first, check quota (it's a SHOULD in draft std) - if (!sendData("HAVESPACE \"" + filename.toUtf8() + "\" " - + QByteArray::number( bufLen ))) - return; - - if (!operationSuccessful()) { - error(ERR_DISK_FULL, i18n("Quota exceeded")); - return; - } -#endif if (!sendData("PUTSCRIPT \"" + filename.toUtf8() + "\" {" + QByteArray::number( bufLen ) + "+}")) return; // atEnd() lies so the code below doesn't work. /*if (!atEnd()) { // We are not expecting any data here, so if the server has responded // with anything but OK we treat it as an error. char * buf = new char[2]; while (!atEnd()) { ksDebug << "Reading..." << endl; read(buf, 1); ksDebug << "Trailing [" << buf[0] << "]" << endl; } ksDebug << "End of data." << endl; delete[] buf; if (!operationSuccessful()) { error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred " "while trying to negotiate script uploading.\n" "The server responded:\n%1") .arg(r.getAction().right(r.getAction().length() - 3))); return; } }*/ // upload data to the server if (write(data, bufLen) != bufLen) { error(ERR_COULD_NOT_WRITE, i18n("Network error.")); disconnect(true); return; } // finishing CR/LF if (!sendData("")) return; processedSize(bufLen); infoMessage(i18n("Verifying upload completion...")); if (operationSuccessful()) ksDebug << "Script upload complete." << endl; else { /* The managesieve server parses received scripts and rejects * scripts which are not syntactically correct. Here we expect * to receive a message detailing the error (only the first * error is reported. */ if (r.getAction().length() > 3) { // make a copy of the extra info QByteArray extra = r.getAction().right(r.getAction().length() - 3); // send the extra message off for re-processing receiveData(false, extra); if (r.getType() == kio_sieveResponse::QUANTITY) { // length of the error message uint len = r.getQuantity(); QByteArray errmsg(len, 0); read(errmsg.data(), len); error(ERR_INTERNAL_SERVER, i18n("The script did not upload successfully.\n" "This is probably due to errors in the script.\n" "The server responded:\n%1", QString::fromLatin1( errmsg.data(), errmsg.size() ) )); // clear the rest of the incoming data receiveData(); } else if (r.getType() == kio_sieveResponse::KEY_VAL_PAIR) { error(ERR_INTERNAL_SERVER, i18n("The script did not upload successfully.\n" "This is probably due to errors in the script.\n" "The server responded:\n%1", QString::fromUtf8(r.getKey()))); } else { error(ERR_INTERNAL_SERVER, i18n("The script did not upload successfully.\n" "The script may contain errors.")); } } else error(ERR_INTERNAL_SERVER, i18n("The script did not upload successfully.\n" "The script may contain errors.")); } //if ( permissions != -1 ) // chmod( url, permissions ); infoMessage(i18nc("data upload complete", "Done.")); finished(); } static void inplace_crlf2lf( QByteArray & in ) { if ( in.isEmpty() ) return; QByteArray & out = in; // inplace const char * s = in.begin(); const char * const end = in.end(); char * d = out.begin(); char last = '\0'; while ( s < end ) { if ( *s == '\n' && last == '\r' ) --d; *d++ = last = *s++; } out.resize( d - out.begin() ); } /* ---------------------------------------------------------------------------------- */ void kio_sieveProtocol::get(const KUrl& url) { changeCheck( url ); if (!connect()) return; infoMessage(i18n("Retrieving data...")); QString filename = url.fileName( KUrl::ObeyTrailingSlash ); if (filename.isEmpty()) { error(ERR_MALFORMED_URL, url.prettyUrl()); return; } //SlaveBase::mimetype( QString("text/plain") ); // "application/sieve"); if (!sendData("GETSCRIPT \"" + filename.toUtf8() + "\"")) return; if (receiveData() && r.getType() == kio_sieveResponse::QUANTITY) { // determine script size ssize_t total_len = r.getQuantity(); totalSize( total_len ); ssize_t recv_len = 0; do { // wait for data... if ( !waitForResponse( 600 ) ) { error( KIO::ERR_SERVER_TIMEOUT, m_sServer ); disconnect( true ); return; } // ...read data... // Only read as much as we need, otherwise we slurp in the OK that // operationSuccessful() is expecting below. QByteArray dat( qMin( total_len - recv_len, ssize_t(64 * 1024 )), '\0' ); ssize_t this_recv_len = read( dat.data(), dat.size() ); if ( this_recv_len < 1 && !isConnected() ) { error( KIO::ERR_CONNECTION_BROKEN, m_sServer ); disconnect( true ); return; } dat.resize( this_recv_len ); inplace_crlf2lf( dat ); // send data to slaveinterface data( dat ); recv_len += this_recv_len; processedSize( recv_len ); } while ( recv_len < total_len ); infoMessage(i18n("Finishing up...") ); data(QByteArray()); if (operationSuccessful()) ksDebug << "Script retrieval complete." << endl; else ksDebug << "Script retrieval failed." << endl; } else { error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred " "while trying to negotiate script downloading.")); return; } infoMessage(i18nc("data retrival complete", "Done.")); finished(); } void kio_sieveProtocol::del(const KUrl &url, bool isfile) { if (!isfile) { error(ERR_INTERNAL, i18n("Folders are not supported.")); return; } changeCheck( url ); if (!connect()) return; infoMessage(i18n("Deleting file...")); QString filename = url.fileName( KUrl::ObeyTrailingSlash ); if (filename.isEmpty()) { error(ERR_MALFORMED_URL, url.prettyUrl()); return; } if (!sendData("DELETESCRIPT \"" + filename.toUtf8() + "\"")) return; if (operationSuccessful()) ksDebug << "Script deletion successful." << endl; else { error(ERR_INTERNAL_SERVER, i18n("The server would not delete the file.")); return; } infoMessage(i18nc("file removal complete", "Done.")); finished(); } void kio_sieveProtocol::chmod(const KUrl& url, int permissions) { switch ( permissions ) { case 0700: // activate activate(url); break; case 0600: // deactivate deactivate(); break; default: // unsupported error(ERR_CANNOT_CHMOD, i18n("Cannot chmod to anything but 0700 (active) or 0600 (inactive script).")); return; } finished(); } void kio_sieveProtocol::urlStat(const KUrl& url) { changeCheck( url ); if (!connect()) return; UDSEntry entry; QString filename = url.fileName( KUrl::ObeyTrailingSlash ); if (filename.isEmpty()) { entry.insert(KIO::UDSEntry::UDS_NAME, QString::fromLatin1("/")); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700); statEntry(entry); } else { if (!sendData("LISTSCRIPTS")) return; while(receiveData()) { if (r.getType() == kio_sieveResponse::ACTION) { if (r.getAction().toLower().count("ok") == 1) // Script list completed break; } else if (filename == QString::fromUtf8(r.getKey())) { entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME,QString::fromUtf8(r.getKey())); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); if ( r.getExtra() == "ACTIVE" ) entry.insert(KIO::UDSEntry::UDS_ACCESS,0700); else entry.insert(KIO::UDSEntry::UDS_ACCESS,0600); entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1( "application/sieve" ) ); //setMetaData("active", (r.getExtra() == "ACTIVE") ? "yes" : "no"); statEntry(entry); // cannot break here because we need to clear // the rest of the incoming data. } } } finished(); } void kio_sieveProtocol::listDir(const KUrl& url) { changeCheck( url ); if (!connect()) return; if (!sendData("LISTSCRIPTS")) return; UDSEntry entry; while(receiveData()) { if (r.getType() == kio_sieveResponse::ACTION) { if (r.getAction().toLower().count("ok") == 1) // Script list completed. break; } else { entry.clear(); entry.insert(KIO::UDSEntry::UDS_NAME,QString::fromUtf8(r.getKey())); entry.insert(KIO::UDSEntry::UDS_FILE_TYPE,S_IFREG); if ( r.getExtra() == "ACTIVE" ) entry.insert(KIO::UDSEntry::UDS_ACCESS, 0700);// mark exec'able else entry.insert(KIO::UDSEntry::UDS_ACCESS,0600); entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1( "application/sieve" ) ); //asetMetaData("active", (r.getExtra() == "ACTIVE") ? "true" : "false"); ksDebug << "Listing script " << r.getKey() << endl; listEntry(entry , false); } } listEntry(entry, true); finished(); } /* ---------------------------------------------------------------------------------- */ bool kio_sieveProtocol::saslInteract( void *in, AuthInfo &ai ) { ksDebug << "sasl_interact" << endl; sasl_interact_t *interact = ( sasl_interact_t * ) in; //some mechanisms do not require username && pass, so it doesn't need a popup //window for getting this info for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { if ( interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS ) { if (m_sUser.isEmpty() || m_sPass.isEmpty()) { if (!openPasswordDialog(ai)) { // calling error() below is wrong for two reasons: // - ERR_ABORTED is too harsh // - higher layers already call error() and that can't happen twice. //error(ERR_ABORTED, i18n("No authentication details supplied.")); return false; } m_sUser = ai.username; m_sPass = ai.password; } break; } } interact = ( sasl_interact_t * ) in; while( interact->id != SASL_CB_LIST_END ) { ksDebug << "SASL_INTERACT id: " << interact->id << endl; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: ksDebug << "SASL_CB_[AUTHNAME|USER]: '" << m_sUser << "'" << endl; interact->result = strdup( m_sUser.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: ksDebug << "SASL_CB_PASS: [hidden] " << endl; interact->result = strdup( m_sPass.toUtf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = NULL; interact->len = 0; break; } interact++; } return true; } #define SASLERROR error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occurred during authentication: %1", \ QString::fromUtf8( sasl_errdetail( conn ) ))); bool kio_sieveProtocol::authenticate() { int result; sasl_conn_t *conn = NULL; sasl_interact_t *client_interact = NULL; const char *out = NULL; uint outlen; const char *mechusing = NULL; QByteArray challenge; /* Retrieve authentication details from user. * Note: should this require realm as well as user & pass details * before it automatically skips the prompt? * Note2: encoding issues with PLAIN login? */ AuthInfo ai; ai.url.setProtocol("sieve"); ai.url.setHost(m_sServer); ai.url.setPort( m_port ); ai.username = m_sUser; ai.password = m_sPass; ai.keepPassword = true; ai.caption = i18n("Sieve Authentication Details"); ai.comment = i18n("Please enter your authentication details for your sieve account " "(usually the same as your email password):"); result = sasl_client_new( "sieve", m_sServer.toLatin1(), 0, 0, callbacks, 0, &conn ); if ( result != SASL_OK ) { ksDebug << "sasl_client_new failed with: " << result << endl; SASLERROR return false; } QStringList strList; // strList.append("NTLM"); if ( !m_sAuth.isEmpty() ) strList.append( m_sAuth ); else strList = m_sasl_caps; do { result = sasl_client_start(conn, strList.join(" ").toLatin1(), &client_interact, &out, &outlen, &mechusing); if (result == SASL_INTERACT) if ( !saslInteract( client_interact, ai ) ) { sasl_dispose( &conn ); return false; }; } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { ksDebug << "sasl_client_start failed with: " << result << endl; SASLERROR sasl_dispose( &conn ); return false; } ksDebug << "Preferred authentication method is " << mechusing << "." << endl; QString firstCommand = "AUTHENTICATE \"" + QString::fromLatin1( mechusing ) + "\""; challenge = QByteArray::fromRawData( out, outlen ).toBase64(); if ( !challenge.isEmpty() ) { firstCommand += " \""; firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); firstCommand += '\"'; } if (!sendData( firstCommand.toLatin1() )) return false; do { receiveData(); if (operationResult() != OTHER) break; ksDebug << "Challenge len " << r.getQuantity() << endl; if (r.getType() != kio_sieveResponse::QUANTITY) { sasl_dispose( &conn ); error(ERR_UNSUPPORTED_PROTOCOL, QString::fromLatin1(mechusing)); return false; } int qty = r.getQuantity(); receiveData(); if (r.getType() != kio_sieveResponse::ACTION && r.getAction().length() != qty) { sasl_dispose( &conn ); error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred during authentication.\n" "Choose a different authentication method to %1.", mechusing)); return false; } challenge = QByteArray::fromBase64( QByteArray::fromRawData( r.getAction().data(), qty ) ); // ksDebug << "S: [" << r.getAction() << "]." << endl; do { result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), challenge.size(), &client_interact, &out, &outlen); if (result == SASL_INTERACT) if ( !saslInteract( client_interact, ai ) ) { sasl_dispose( &conn ); return false; }; } while ( result == SASL_INTERACT ); ksDebug << "sasl_client_step: " << result << endl; if ( result != SASL_CONTINUE && result != SASL_OK ) { ksDebug << "sasl_client_step failed with: " << result << endl; SASLERROR sasl_dispose( &conn ); return false; } sendData('\"' + QByteArray::fromRawData( out, outlen ).toBase64() + '\"'); // ksDebug << "C-1: [" << out << "]." << endl; } while ( true ); ksDebug << "Challenges finished." << endl; sasl_dispose( &conn ); if (operationResult() == OK) { // Authentication succeeded. return true; } else { // Authentication failed. error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed.\nMost likely the password is wrong.\nThe server responded:\n%1", QString::fromLatin1( r.getAction() ) ) ); return false; } } /* --------------------------------------------------------------------------- */ void kio_sieveProtocol::mimetype(const KUrl & url) { ksDebug << "Requesting mimetype for " << url.prettyUrl() << endl; if (url.fileName( KUrl::ObeyTrailingSlash ).isEmpty()) mimeType( "inode/directory" ); else mimeType( "application/sieve" ); finished(); } /* --------------------------------------------------------------------------- */ bool kio_sieveProtocol::sendData(const QByteArray &data) { QByteArray write_buf = data + "\r\n"; //ksDebug << "C: " << data << endl; // Write the command ssize_t write_buf_len = write_buf.length(); if (write(write_buf.data(), write_buf_len) != write_buf_len) { error(ERR_COULD_NOT_WRITE, i18n("Network error.")); disconnect(true); return false; } return true; } /* --------------------------------------------------------------------------- */ bool kio_sieveProtocol::receiveData(bool waitForData, const QByteArray &reparse) { QByteArray interpret; int start, end; if ( reparse.isEmpty() ) { if (!waitForData) // is there data waiting? if (atEnd()) return false; // read data from the server char buffer[SIEVE_DEFAULT_RECIEVE_BUFFER]; const ssize_t numRead = readLine(buffer, SIEVE_DEFAULT_RECIEVE_BUFFER - 1); if ( numRead < 0 ) return false; buffer[SIEVE_DEFAULT_RECIEVE_BUFFER-1] = '\0'; // strip LF/CR interpret = QByteArray(buffer, qstrlen(buffer) - 2); } else { interpret = reparse; } r.clear(); //ksDebug << "S: " << interpret << endl; switch(interpret[0]) { case '{': { // expecting {quantity} start = 0; end = interpret.indexOf("+}", start + 1); // some older versions of Cyrus enclose the literal size just in { } instead of { +} if ( end == -1 ) end = interpret.indexOf('}', start + 1); bool ok = false; r.setQuantity(interpret.mid(start + 1, end - start - 1).toUInt( &ok )); if (!ok) { disconnect(); error(ERR_INTERNAL_SERVER, i18n("A protocol error occurred.")); return false; } return true; } case '"': // expecting "key" "value" pairs break; default: // expecting single string r.setAction(interpret); return true; } start = 0; end = interpret.indexOf('"', start + 1); if (end == -1) { ksDebug << "Possible insufficient buffer size." << endl; r.setKey(interpret.right(interpret.length() - start)); return true; } r.setKey(interpret.mid(start + 1, end - start - 1)); start = interpret.indexOf('"', end + 1); if (start == -1) { if ((int)interpret.length() > end) // skip " and space r.setExtra(interpret.right(interpret.length() - end - 2)); return true; } end = interpret.indexOf('"', start + 1); if (end == -1) { ksDebug << "Possible insufficient buffer size." << endl; r.setVal(interpret.right(interpret.length() - start)); return true; } r.setVal(interpret.mid(start + 1, end - start - 1)); return true; } bool kio_sieveProtocol::operationSuccessful() { while (receiveData(true)) { if (r.getType() == kio_sieveResponse::ACTION) { QByteArray response = r.getAction().left(2); if (response == "OK") { return true; } else if (response == "NO") { return false; } } } return false; } int kio_sieveProtocol::operationResult() { if (r.getType() == kio_sieveResponse::ACTION) { QByteArray response = r.getAction().left(2); if (response == "OK") { return OK; } else if (response == "NO") { return NO; } else if (response == "BY"/*E*/) { return BYE; } } return OTHER; } bool kio_sieveProtocol::requestCapabilitiesAfterStartTLS() const { // Cyrus didn't send CAPABILITIES after STARTTLS until 2.3.11, which is // not standard conform, but we need to support that anyway. // m_implementation looks like this 'Cyrus timsieved v2.2.12' for Cyrus btw. QRegExp regExp( "Cyrus\\stimsieved\\sv(\\d+)\\.(\\d+)\\.(\\d+)([-\\w]*)", Qt::CaseInsensitive ); if ( regExp.indexIn( m_implementation ) >= 0 ) { const int major = regExp.cap( 1 ).toInt(); const int minor = regExp.cap( 2 ).toInt(); const int patch = regExp.cap( 3 ).toInt(); const QString vendor = regExp.cap( 4 ); if ( major < 2 || (major == 2 && (minor < 3 || (minor == 3 && patch < 11))) || (vendor == "-kolab-nocaps") ) { ksDebug << k_funcinfo << "Enabling compat mode for Cyrus < 2.3.11 or Cyrus marked as \"kolab-nocaps\"" << endl; return true; } } return false; } diff --git a/kioslave/smtp/capabilities.cpp b/kioslave/smtp/capabilities.cpp index 892b366bf..00f51835f 100644 --- a/kioslave/smtp/capabilities.cpp +++ b/kioslave/smtp/capabilities.cpp @@ -1,114 +1,116 @@ /* -*- c++ -*- capabilities.cc This file is part of kio_smtp, the KDE SMTP kioslave. Copyright (c) 2003 Marc Mutz 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( *it ); return c; } void Capabilities::add( const QString & cap, bool replace ) { QStringList tokens = cap.toUpper().split( ' '); 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( "STARTTLS" ); result += saslMethodsQSL(); if ( have( "PIPELINING" ) ) result.push_back( "PIPELINING" ); if ( have( "8BITMIME" ) ) result.push_back( "8BITMIME" ); if ( have( "SIZE" ) ) { bool ok = false; unsigned int size = 0; if ( !mCapabilities["SIZE"].isEmpty() ) mCapabilities["SIZE"].front().toUInt( &ok ); if ( ok && !size ) result.push_back( "SIZE=*" ); // any size else if ( ok ) result.push_back( "SIZE=" + QString::number( size ) ); // fixed max else result.push_back( "SIZE" ); // indetermined } return result.join( " " ); } QStringList Capabilities::saslMethodsQSL() const { QStringList result; - for ( QMap::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { - if ( it.key() == "AUTH" ) + for ( QMap::const_iterator it = mCapabilities.begin(); + it != mCapabilities.end(); ++it ) { + if ( it.key() == "AUTH" ) { result += it.value(); - else if ( it.key().startsWith( "AUTH=" ) ) { + } else if ( it.key().startsWith( QLatin1String( "AUTH=" ) ) ) { result.push_back( it.key().mid( qstrlen("AUTH=") ) ); result += it.value(); } } result.sort(); for (int i = 0, j = 1; j < result.count(); i = j++ ) { - if ( result.at(i) == result.at(j) ) + if ( result.at(i) == result.at(j) ) { result.removeAt( j-- ); + } } return result; } } // namespace KioSMTP diff --git a/kioslave/smtp/tests/interactivesmtpserver.cpp b/kioslave/smtp/tests/interactivesmtpserver.cpp index 2030b71a0..9c98b3e4d 100644 --- a/kioslave/smtp/tests/interactivesmtpserver.cpp +++ b/kioslave/smtp/tests/interactivesmtpserver.cpp @@ -1,206 +1,205 @@ /* -*- 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 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 #include #include #include #include #include #include #include #include #include #include #include "interactivesmtpserver.h" static const QHostAddress localhost( 0x7f000001 ); // 127.0.0.1 static QString err2str( QAbstractSocket::SocketError error ) { switch ( error ) { case QAbstractSocket::ConnectionRefusedError: return "Connection refused"; case QAbstractSocket::HostNotFoundError: return "Host not found"; default: return "Unknown error"; } } static QString escape( QString s ) { return s .replace( '&', "&" ) .replace( '>', ">" ) .replace( '<', "<" ) .replace( '"', """ ) ; } static QString trim( const QString & s ) { if ( s.endsWith( "\r\n" ) ) return s.left( s.length() - 2 ); if ( s.endsWith( "\r" ) || s.endsWith( "\n" ) ) return s.left( s.length() - 1 ); return s; } InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow() { if ( mSocket ) { mSocket->close(); if ( mSocket->state() == QAbstractSocket::ClosingState ) connect( mSocket, SIGNAL(delayedCloseFinished()), mSocket, SLOT(deleteLater()) ); else mSocket->deleteLater(); mSocket = 0; } } void InteractiveSMTPServerWindow::slotSendResponse() { const QString line = mLineEdit->text(); mLineEdit->clear(); QTextStream s( mSocket ); s << line + "\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 ); - vlay->setSpacing( 6 ); 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( "&Response:", this ); mLabel->setBuddy( mLineEdit ); but = new QPushButton( "&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( "&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( "220 hi there" ); mLineEdit->setFocus(); } void InteractiveSMTPServerWindow::slotDisplayClient( const QString & s ) { mTextEdit->append( "C:" + escape(s) ); } void InteractiveSMTPServerWindow::slotDisplayServer( const QString & s ) { mTextEdit->append( "S:" + escape(s) ); } void InteractiveSMTPServerWindow::slotDisplayMeta( const QString & s ) { mTextEdit->append( "" + escape(s) + "" ); } void InteractiveSMTPServerWindow::slotReadyRead() { while ( mSocket->canReadLine() ) slotDisplayClient( trim( mSocket->readLine() ) ); } void InteractiveSMTPServerWindow::slotError( QAbstractSocket::SocketError error ) { slotDisplayMeta( QString( "E: %1" ).arg( err2str( error ) ) ); } void InteractiveSMTPServerWindow::slotConnectionClosed() { slotDisplayMeta( "Connection closed by peer" ); } void InteractiveSMTPServerWindow::slotCloseConnection() { mSocket->close(); } #include "interactivesmtpserver.moc" diff --git a/kioslave/smtp/transactionstate.h b/kioslave/smtp/transactionstate.h index 88c322af0..a7c26d461 100644 --- a/kioslave/smtp/transactionstate.h +++ b/kioslave/smtp/transactionstate.h @@ -1,194 +1,194 @@ /* -*- c++ -*- transactionstate.h This file is part of kio_smtp, the KDE SMTP kioslave. Copyright (c) 2003 Marc Mutz 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 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 - neccessarily fail the whole transaction (servers are free to + 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 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/kldap/ldapconfigwidget.cpp b/kldap/ldapconfigwidget.cpp index fbe1423eb..1472d9a37 100644 --- a/kldap/ldapconfigwidget.cpp +++ b/kldap/ldapconfigwidget.cpp @@ -1,890 +1,889 @@ /* This file is part of libkldap. Copyright (c) 2004-2006 Szombathelyi György 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 "ldapconfigwidget.h" #include "ldapsearch.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KLDAP; class LdapConfigWidget::Private { public: Private( LdapConfigWidget *parent ) : mParent( parent ), mFeatures( W_ALL ), mProg( 0 ) { mainLayout = new QGridLayout( mParent ); mainLayout->setMargin( 0 ); - mainLayout->setSpacing( KDialog::spacingHint() ); } void setLDAPPort(); void setLDAPSPort(); void setAnonymous( bool on ); void setSimple( bool on ); void setSASL( bool on ); void queryDNClicked(); void queryMechClicked(); void loadData( LdapSearch *search, const LdapObject &object ); void loadResult( LdapSearch *search ); void sendQuery(); void initWidget(); LdapConfigWidget *mParent; WinFlags mFeatures; QStringList mQResult; QString mAttr; KLineEdit *mUser; KLineEdit *mPassword; KLineEdit *mHost; QSpinBox *mPort, *mVersion, *mSizeLimit, *mTimeLimit, *mPageSize; KLineEdit *mDn, *mBindDn, *mRealm; KLineEdit *mFilter; QRadioButton *mAnonymous,*mSimple,*mSASL; QCheckBox *mSubTree; QPushButton *mEditButton; QPushButton *mQueryMech; QRadioButton *mSecNo,*mSecTLS,*mSecSSL; KComboBox *mMech; bool mCancelled; KProgressDialog *mProg; QGridLayout *mainLayout; }; void LdapConfigWidget::Private::initWidget() { QLabel *label; mUser = mPassword = mHost = mDn = mBindDn = mRealm = mFilter = 0; mPort = mVersion = mTimeLimit = mSizeLimit = 0; mAnonymous = mSimple = mSASL = mSecNo = mSecTLS = mSecSSL = 0; mEditButton = mQueryMech = 0; mPageSize = 0; mMech = 0; int row = 0; int col; if ( mFeatures & W_USER ) { label = new QLabel( i18n( "User:" ), mParent ); mUser = new KLineEdit( mParent ); mUser->setObjectName( "kcfg_ldapuser" ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mUser, row, 1, 1, 3 ); row++; } if ( mFeatures & W_BINDDN ) { label = new QLabel( i18n( "Bind DN:" ), mParent ); mBindDn = new KLineEdit( mParent ); mBindDn->setObjectName( "kcfg_ldapbinddn" ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mBindDn, row, 1, 1, 3 ); row++; } if ( mFeatures & W_REALM ) { label = new QLabel( i18n( "Realm:" ), mParent ); mRealm = new KLineEdit( mParent ); mRealm->setObjectName( "kcfg_ldaprealm" ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mRealm, row, 1, 1, 3 ); row++; } if ( mFeatures & W_PASS ) { label = new QLabel( i18n( "Password:" ), mParent ); mPassword = new KLineEdit( mParent ); mPassword->setObjectName( "kcfg_ldappassword" ); mPassword->setEchoMode( KLineEdit::Password ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mPassword, row, 1, 1, 3 ); row++; } if ( mFeatures & W_HOST ) { label = new QLabel( i18n( "Host:" ), mParent ); mHost = new KLineEdit( mParent ); mHost->setObjectName( "kcfg_ldaphost" ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mHost, row, 1, 1, 3 ); row++; } col = 0; if ( mFeatures & W_PORT ) { label = new QLabel( i18n( "Port:" ), mParent ); mPort = new QSpinBox( mParent ); mPort->setRange( 0, 65535 ); mPort->setObjectName( "kcfg_ldapport" ); mPort->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ) ); mPort->setValue( 389 ); mainLayout->addWidget( label, row, col ); mainLayout->addWidget( mPort, row, col+1 ); col += 2; } if ( mFeatures & W_VER ) { label = new QLabel( i18n( "LDAP version:" ), mParent ); mVersion = new QSpinBox( mParent ); mVersion->setRange( 2, 3 ); mVersion->setObjectName( "kcfg_ldapver" ); mVersion->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ) ); mVersion->setValue( 3 ); mainLayout->addWidget( label, row, col ); mainLayout->addWidget( mVersion, row, col+1 ); } if ( mFeatures & ( W_PORT | W_VER ) ) { row++; } col = 0; if ( mFeatures & W_SIZELIMIT ) { label = new QLabel( i18n( "Size limit:" ), mParent ); mSizeLimit = new QSpinBox( mParent ); mSizeLimit->setRange( 0, 9999999 ); mSizeLimit->setObjectName( "kcfg_ldapsizelimit" ); mSizeLimit->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ) ); mSizeLimit->setValue( 0 ); mSizeLimit->setSpecialValueText( i18nc( "default ldap size limit", "Default" ) ); mainLayout->addWidget( label, row, col ); mainLayout->addWidget( mSizeLimit, row, col+1 ); col += 2; } if ( mFeatures & W_TIMELIMIT ) { label = new QLabel( i18n( "Time limit:" ), mParent ); mTimeLimit = new QSpinBox( mParent ); mTimeLimit->setRange( 0, 9999999 ); mTimeLimit->setObjectName( "kcfg_ldaptimelimit" ); mTimeLimit->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ) ); mTimeLimit->setValue( 0 ); mTimeLimit->setSuffix( i18n( " sec" ) ); mTimeLimit->setSpecialValueText( i18nc( "default ldap time limit", "Default" ) ); mainLayout->addWidget( label, row, col ); mainLayout->addWidget( mTimeLimit, row, col+1 ); } if ( mFeatures & ( W_SIZELIMIT | W_TIMELIMIT ) ) { row++; } if ( mFeatures & W_PAGESIZE ) { label = new QLabel( i18n( "Page size:" ), mParent ); mPageSize = new QSpinBox( mParent ); mPageSize->setRange( 0, 9999999 ); mPageSize->setObjectName( "kcfg_ldappagesize" ); mPageSize->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred ) ); mPageSize->setValue( 0 ); mPageSize->setSpecialValueText( i18n( "No paging" ) ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mPageSize, row++, 1 ); } if ( mFeatures & W_DN ) { label = new QLabel( i18nc( "Distinguished Name", "DN:" ), mParent ); mDn = new KLineEdit( mParent ); mDn->setObjectName( "kcfg_ldapdn" ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mDn, row, 1, 1, 1 ); //without host query doesn't make sense if ( mHost ) { QPushButton *dnquery = new QPushButton( i18n( "Query Server" ), mParent ); connect( dnquery, SIGNAL( clicked() ), mParent, SLOT( queryDNClicked() ) ); mainLayout->addWidget( dnquery, row, 2, 1, 1 ); } row++; } if ( mFeatures & W_FILTER ) { label = new QLabel( i18n( "Filter:" ), mParent ); mFilter = new KLineEdit( mParent ); mFilter->setObjectName( "kcfg_ldapfilter" ); mainLayout->addWidget( label, row, 0 ); mainLayout->addWidget( mFilter, row, 1, 1, 3 ); row++; } if ( mFeatures & W_SECBOX ) { QGroupBox *btgroup = new QGroupBox( i18n( "Security" ), mParent ); QHBoxLayout *hbox = new QHBoxLayout; btgroup->setLayout( hbox ); mSecNo = new QRadioButton( i18nc( "@option:radio set no security", "No" ), btgroup ); mSecNo->setObjectName( "kcfg_ldapnosec" ); hbox->addWidget( mSecNo ); mSecTLS = new QRadioButton( i18nc( "@option:radio use TLS security", "TLS" ), btgroup ); mSecTLS->setObjectName( "kcfg_ldaptls" ); hbox->addWidget( mSecTLS ); mSecSSL = new QRadioButton( i18nc( "@option:radio use SSL security", "SSL" ), btgroup ); mSecSSL->setObjectName( "kcfg_ldapssl" ); hbox->addWidget( mSecSSL ); mainLayout->addWidget( btgroup, row, 0, 1, 4 ); connect( mSecNo, SIGNAL( clicked() ), mParent, SLOT( setLDAPPort() ) ); connect( mSecTLS, SIGNAL( clicked() ), mParent, SLOT( setLDAPPort() ) ); connect( mSecSSL, SIGNAL( clicked() ), mParent, SLOT( setLDAPSPort( ) ) ); mSecNo->setChecked( true ); row++; } if ( mFeatures & W_AUTHBOX ) { QGroupBox *authbox = new QGroupBox( i18n( "Authentication" ), mParent ); QVBoxLayout *vbox = new QVBoxLayout; authbox->setLayout( vbox ); QHBoxLayout *hbox = new QHBoxLayout; vbox->addLayout( hbox ); mAnonymous = new QRadioButton( i18nc( "@option:radio anonymous authentication", "Anonymous" ), authbox ); mAnonymous->setObjectName( "kcfg_ldapanon" ); hbox->addWidget( mAnonymous ); mSimple = new QRadioButton( i18nc( "@option:radio simple authentication", "Simple" ), authbox ); mSimple->setObjectName( "kcfg_ldapsimple" ); hbox->addWidget( mSimple ); mSASL = new QRadioButton( i18nc( "@option:radio SASL authentication", "SASL" ), authbox ); mSASL->setObjectName( "kcfg_ldapsasl" ); hbox->addWidget( mSASL ); hbox = new QHBoxLayout; vbox->addLayout( hbox ); label = new QLabel( i18n( "SASL mechanism:" ), authbox ); hbox->addWidget( label ); mMech = new KComboBox( false, authbox ); mMech->setObjectName( "kcfg_ldapsaslmech" ); mMech->setEditable( true ); mMech->addItem( "DIGEST-MD5" ); mMech->addItem( "GSSAPI" ); mMech->addItem( "PLAIN" ); hbox->addWidget( mMech ); //without host query doesn't make sense if ( mHost ) { mQueryMech = new QPushButton( i18n( "Query Server" ), authbox ); hbox->addWidget( mQueryMech ); connect( mQueryMech, SIGNAL( clicked() ), mParent, SLOT( queryMechClicked() ) ); } mainLayout->addWidget( authbox, row, 0, 2, 4 ); connect( mAnonymous, SIGNAL( toggled( bool ) ), mParent, SLOT( setAnonymous( bool ) ) ); connect( mSimple, SIGNAL( toggled( bool ) ), mParent, SLOT( setSimple( bool ) ) ); connect( mSASL, SIGNAL( toggled( bool ) ), mParent, SLOT( setSASL( bool ) ) ); mAnonymous->setChecked( true ); } } void LdapConfigWidget::Private::sendQuery() { LdapUrl _url; mQResult.clear(); mCancelled = true; _url.setProtocol( ( mSecSSL && mSecSSL->isChecked() ) ? "ldaps" : "ldap" ); if ( mHost ) { _url.setHost( mHost->text() ); } if ( mPort ) { _url.setPort( mPort->value() ); } _url.setDn( LdapDN( "" ) ); _url.setAttributes( QStringList( mAttr ) ); _url.setScope( LdapUrl::Base ); if ( mVersion ) { _url.setExtension( "x-ver", QString::number( mVersion->value() ) ); } if ( mSecTLS && mSecTLS->isChecked() ) { _url.setExtension( "x-tls", "" ); } kDebug() << "sendQuery url:" << _url.prettyUrl(); LdapSearch search; connect( &search, SIGNAL( data( KLDAP::LdapSearch*, const KLDAP::LdapObject& ) ), mParent, SLOT( loadData( KLDAP::LdapSearch*, const KLDAP::LdapObject& ) ) ); connect( &search, SIGNAL( result( KLDAP::LdapSearch* ) ), mParent, SLOT( loadResult( KLDAP::LdapSearch* ) ) ); if ( !search.search( _url ) ) { KMessageBox::error( mParent, search.errorString() ); return; } if ( mProg == 0 ) { mProg = new KProgressDialog( mParent ); mProg->setWindowTitle( i18n( "LDAP Query" ) ); mProg->setModal( true ); } mProg->setLabelText( _url.prettyUrl() ); mProg->progressBar()->setRange( 0, 1 ); mProg->progressBar()->setValue( 0 ); mProg->exec(); if ( mCancelled ) { kDebug() << "query canceled!"; search.abandon(); } else { if ( search.error() ) { KMessageBox::error( mParent, search.errorString() ); } } } void LdapConfigWidget::Private::queryMechClicked() { mAttr = "supportedsaslmechanisms"; sendQuery(); if ( !mQResult.isEmpty() ) { mQResult.sort(); mMech->clear(); mMech->addItems( mQResult ); } } void LdapConfigWidget::Private::queryDNClicked() { mAttr = "namingcontexts"; sendQuery(); if ( !mQResult.isEmpty() ) { mDn->setText( mQResult.first() ); } } void LdapConfigWidget::Private::loadData( LdapSearch *, const LdapObject &object ) { kDebug() << "object:" << object.toString(); mProg->progressBar()->setValue( mProg->progressBar()->value() + 1 ); for ( LdapAttrMap::ConstIterator it = object.attributes().constBegin(); it != object.attributes().constEnd(); ++it ) { for ( LdapAttrValue::ConstIterator it2 = (*it).constBegin(); it2 != (*it).constEnd(); ++it2 ) { mQResult.push_back( QString::fromUtf8( *it2 ) ); } } } void LdapConfigWidget::Private::loadResult( LdapSearch *search ) { Q_UNUSED( search ); mCancelled = false; mProg->close(); } void LdapConfigWidget::Private::setAnonymous( bool on ) { if ( !on ) { return; } if ( mUser ) { mUser->setEnabled( false ); } if ( mPassword ) { mPassword->setEnabled( false ); } if ( mBindDn ) { mBindDn->setEnabled( false ); } if ( mRealm ) { mRealm->setEnabled( false ); } if ( mMech ) { mMech->setEnabled( false ); } if ( mQueryMech ) { mQueryMech->setEnabled( false ); } } void LdapConfigWidget::Private::setSimple( bool on ) { if ( !on ) { return; } if ( mUser ) { mUser->setEnabled( false ); } if ( mPassword ) { mPassword->setEnabled( true ); } if ( mBindDn ) { mBindDn->setEnabled( true ); } if ( mRealm ) { mRealm->setEnabled( false ); } if ( mMech ) { mMech->setEnabled( false ); } if ( mQueryMech ) { mQueryMech->setEnabled( false ); } } void LdapConfigWidget::Private::setSASL( bool on ) { if ( !on ) { return; } if ( mUser ) { mUser->setEnabled( true ); } if ( mPassword ) { mPassword->setEnabled( true ); } if ( mBindDn ) { mBindDn->setEnabled( true ); } if ( mRealm ) { mRealm->setEnabled( true ); } if ( mMech ) { mMech->setEnabled( true ); } if ( mQueryMech ) { mQueryMech->setEnabled( true ); } } void LdapConfigWidget::Private::setLDAPPort() { if ( mPort ) { mPort->setValue( 389 ); } } void LdapConfigWidget::Private::setLDAPSPort() { if ( mPort ) { mPort->setValue( 636 ); } } LdapConfigWidget::LdapConfigWidget( QWidget *parent, Qt::WFlags fl ) : QWidget( parent, fl ), d( new Private( this ) ) { } LdapConfigWidget::LdapConfigWidget( LdapConfigWidget::WinFlags flags, QWidget *parent, Qt::WFlags fl ) : QWidget( parent, fl ), d( new Private( this ) ) { d->mFeatures = flags; d->initWidget(); } LdapConfigWidget::~LdapConfigWidget() { delete d; } LdapUrl LdapConfigWidget::url() const { return server().url(); } void LdapConfigWidget::setUrl( const LdapUrl &url ) { LdapServer _server; _server.setUrl( url ); setServer( _server ); } LdapServer LdapConfigWidget::server() const { LdapServer _server; if ( d->mSecSSL && d->mSecSSL->isChecked() ) { _server.setSecurity( LdapServer::SSL ); } else if ( d->mSecTLS && d->mSecTLS->isChecked() ) { _server.setSecurity( LdapServer::TLS ); } else { _server.setSecurity( LdapServer::None ); } if ( d->mUser ) { _server.setUser( d->mUser->text() ); } if ( d->mBindDn ) { _server.setBindDn( d->mBindDn->text() ); } if ( d->mPassword ) { _server.setPassword( d->mPassword->text() ); } if ( d->mRealm ) { _server.setRealm( d->mRealm->text() ); } if ( d->mHost ) { _server.setHost( d->mHost->text() ); } if ( d->mPort ) { _server.setPort( d->mPort->value() ); } if ( d->mDn ) { _server.setBaseDn( LdapDN( d->mDn->text() ) ); } if ( d->mFilter ) { _server.setFilter( d->mFilter->text() ); } if ( d->mVersion ) { _server.setVersion( d->mVersion->value() ); } if ( d->mSizeLimit && d->mSizeLimit->value() != 0 ) { _server.setSizeLimit( d->mSizeLimit->value() ); } if ( d->mTimeLimit && d->mTimeLimit->value() != 0 ) { _server.setTimeLimit( d->mTimeLimit->value() ); } if ( d->mPageSize && d->mPageSize->value() != 0 ) { _server.setPageSize( d->mPageSize->value() ); } if ( d->mAnonymous && d->mAnonymous->isChecked() ) { _server.setAuth( LdapServer::Anonymous ); } else if ( d->mSimple && d->mSimple->isChecked() ) { _server.setAuth( LdapServer::Simple ); } else if ( d->mSASL && d->mSASL->isChecked() ) { _server.setAuth( LdapServer::SASL ); _server.setMech( d->mMech->currentText() ); } return _server; } void LdapConfigWidget::setServer( const LdapServer &server ) { switch ( server.security() ) { case LdapServer::SSL: if ( d->mSecSSL ) { d->mSecSSL->setChecked( true ); } case LdapServer::TLS: if ( d->mSecTLS ) { d->mSecTLS->setChecked( true ); } case LdapServer::None: if ( d->mSecNo ) { d->mSecNo->setChecked( true ); } } switch ( server.auth() ) { case LdapServer::Anonymous: if ( d->mAnonymous ) { d->mAnonymous->setChecked( true ); } case LdapServer::Simple: if ( d->mSimple ) { d->mSimple->setChecked( true ); } case LdapServer::SASL: if ( d->mSASL ) { d->mSASL->setChecked( true ); } } setUser( server.user() ); setBindDn( server.bindDn() ); setPassword( server.password() ); setRealm( server.realm() ); setHost( server.host() ); setPort( server.port() ); setFilter( server.filter() ); setDn( server.baseDn() ); setVersion( server.version() ); setSizeLimit( server.sizeLimit() ); setTimeLimit( server.timeLimit() ); setPageSize( server.pageSize() ); setMech( server.mech() ); } void LdapConfigWidget::setUser( const QString &user ) { if ( d->mUser ) { d->mUser->setText( user ); } } QString LdapConfigWidget::user() const { return d->mUser ? d->mUser->text() : QString(); } void LdapConfigWidget::setPassword( const QString &password ) { if ( d->mPassword ) { d->mPassword->setText( password ); } } QString LdapConfigWidget::password() const { return d->mPassword ? d->mPassword->text() : QString(); } void LdapConfigWidget::setBindDn( const QString &binddn ) { if ( d->mBindDn ) { d->mBindDn->setText( binddn ); } } QString LdapConfigWidget::bindDn() const { return d->mBindDn ? d->mBindDn->text() : QString(); } void LdapConfigWidget::setRealm( const QString &realm ) { if ( d->mRealm ) { d->mRealm->setText( realm ); } } QString LdapConfigWidget::realm() const { return d->mRealm ? d->mRealm->text() : QString(); } void LdapConfigWidget::setHost( const QString &host ) { if ( d->mHost ) { d->mHost->setText( host ); } } QString LdapConfigWidget::host() const { return d->mHost ? d->mHost->text() : QString(); } void LdapConfigWidget::setPort( int port ) { if ( d->mPort ) { d->mPort->setValue( port ); } } int LdapConfigWidget::port() const { return d->mPort ? d->mPort->value() : 389; } void LdapConfigWidget::setVersion( int version ) { if ( d->mVersion ) { d->mVersion->setValue( version ); } } int LdapConfigWidget::version() const { return d->mVersion ? d->mVersion->value() : 3; } void LdapConfigWidget::setDn( const LdapDN &dn ) { if ( d->mDn ) { d->mDn->setText( dn.toString() ); } } LdapDN LdapConfigWidget::dn() const { return d->mDn ? LdapDN( d->mDn->text() ) : LdapDN(); } void LdapConfigWidget::setFilter( const QString &filter ) { if ( d->mFilter ) { d->mFilter->setText( filter ); } } QString LdapConfigWidget::filter() const { return d->mFilter ? d->mFilter->text() : QString(); } void LdapConfigWidget::setMech( const QString &mech ) { if ( d->mMech == 0 ) { return; } if ( !mech.isEmpty() ) { int i = 0; while ( i < d->mMech->count() ) { if ( d->mMech->itemText( i ) == mech ) { break; } i++; } if ( i == d->mMech->count() ) { d->mMech->addItem( mech ); } d->mMech->setCurrentIndex( i ); } } QString LdapConfigWidget::mech() const { return d->mMech ? d->mMech->currentText() : QString(); } void LdapConfigWidget::setSecurity( Security security ) { switch ( security ) { case None: d->mSecNo->setChecked( true ); break; case SSL: d->mSecSSL->setChecked( true ); break; case TLS: d->mSecTLS->setChecked( true ); break; } } LdapConfigWidget::Security LdapConfigWidget::security() const { if ( d->mSecTLS->isChecked() ) { return TLS; } if ( d->mSecSSL->isChecked() ) { return SSL; } return None; } void LdapConfigWidget::setAuth( Auth auth ) { switch ( auth ) { case Anonymous: d->mAnonymous->setChecked( true ); break; case Simple: d->mSimple->setChecked( true ); break; case SASL: d->mSASL->setChecked( true ); break; } } LdapConfigWidget::Auth LdapConfigWidget::auth() const { if ( d->mSimple->isChecked() ) { return Simple; } if ( d->mSASL->isChecked() ) { return SASL; } return Anonymous; } void LdapConfigWidget::setSizeLimit( int sizelimit ) { if ( d->mSizeLimit ) { d->mSizeLimit->setValue( sizelimit ); } } int LdapConfigWidget::sizeLimit() const { return d->mSizeLimit ? d->mSizeLimit->value() : 0; } void LdapConfigWidget::setTimeLimit( int timelimit ) { if ( d->mTimeLimit ) { d->mTimeLimit->setValue( timelimit ); } } int LdapConfigWidget::timeLimit() const { return d->mTimeLimit ? d->mTimeLimit->value() : 0; } void LdapConfigWidget::setPageSize( int pagesize ) { if ( d->mPageSize ) { d->mPageSize->setValue( pagesize ); } } int LdapConfigWidget::pageSize() const { return d->mPageSize ? d->mPageSize->value() : 0; } LdapConfigWidget::WinFlags LdapConfigWidget::features() const { return d->mFeatures; } void LdapConfigWidget::setFeatures( LdapConfigWidget::WinFlags features ) { d->mFeatures = features; // First delete all the child widgets. // FIXME: I hope it's correct QList ch = children(); for ( int i = 0; i < ch.count(); ++i ) { QWidget *widget = dynamic_cast( ch[ i ] ); if ( widget && widget->parent() == this ) { delete ( widget ); } } // Re-create child widgets according to the new flags d->initWidget(); } #include "ldapconfigwidget.moc" diff --git a/kpimidentities/signatureconfigurator.cpp b/kpimidentities/signatureconfigurator.cpp index 10a082445..802f3d75e 100644 --- a/kpimidentities/signatureconfigurator.cpp +++ b/kpimidentities/signatureconfigurator.cpp @@ -1,421 +1,418 @@ /* -*- c++ -*- Copyright 2008 Thomas McGuire Copyright 2008 Edwin Schepers Copyright 2004 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "signatureconfigurator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIMIdentities; namespace KPIMIdentities { /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KPIMIdentities::SignatureConfigurator::Private { public: bool inlinedHtml; }; //@endcond SignatureConfigurator::SignatureConfigurator( QWidget * parent ) : QWidget( parent ), d( new Private ) { // tmp. vars: QLabel * label; QWidget * page; QHBoxLayout * hlay; QVBoxLayout * vlay; QVBoxLayout * page_vlay; vlay = new QVBoxLayout( this ); vlay->setObjectName( "main layout" ); - vlay->setSpacing( KDialog::spacingHint() ); vlay->setMargin( 0 ); // "enable signatue" checkbox: mEnableCheck = new QCheckBox( i18n("&Enable signature"), this ); mEnableCheck->setWhatsThis( i18n("Check this box if you want KMail to append a signature to mails " "written with this identity.")); vlay->addWidget( mEnableCheck ); // "obtain signature text from" combo and label: hlay = new QHBoxLayout(); // inherits spacing vlay->addLayout( hlay ); mSourceCombo = new KComboBox( this ); mSourceCombo->setEditable( false ); mSourceCombo->setWhatsThis( i18n("Click on the widgets below to obtain help on the input methods.")); mSourceCombo->setEnabled( false ); // since !mEnableCheck->isChecked() mSourceCombo->addItems( QStringList() << i18nc("continuation of \"obtain signature text from\"", "Input Field Below") << i18nc("continuation of \"obtain signature text from\"", "File") << i18nc("continuation of \"obtain signature text from\"", "Output of Command") ); label = new QLabel( i18n("Obtain signature &text from:"), this ); label->setBuddy( mSourceCombo ); label->setEnabled( false ); // since !mEnableCheck->isChecked() hlay->addWidget( label ); hlay->addWidget( mSourceCombo, 1 ); // widget stack that is controlled by the source combo: QStackedWidget * widgetStack = new QStackedWidget( this ); widgetStack->setEnabled( false ); // since !mEnableCheck->isChecked() vlay->addWidget( widgetStack, 1 ); connect( mSourceCombo, SIGNAL(currentIndexChanged(int)), widgetStack, SLOT(setCurrentIndex (int)) ); connect( mSourceCombo, SIGNAL(highlighted(int)), widgetStack, SLOT(setCurrentIndex (int)) ); // connects for the enabling of the widgets depending on // signatureEnabled: connect( mEnableCheck, SIGNAL(toggled(bool)), mSourceCombo, SLOT(setEnabled(bool)) ); connect( mEnableCheck, SIGNAL(toggled(bool)), widgetStack, SLOT(setEnabled(bool)) ); connect( mEnableCheck, SIGNAL(toggled(bool)), label, SLOT(setEnabled(bool)) ); // The focus might be still in the widget that is disabled connect( mEnableCheck, SIGNAL(clicked()), mEnableCheck, SLOT(setFocus()) ); int pageno = 0; // page 0: input field for direct entering: page = new QWidget( widgetStack ); widgetStack->insertWidget( pageno, page ); page_vlay = new QVBoxLayout( page ); mEditToolBar = new KToolBar( this ); mEditToolBar->setToolButtonStyle( Qt::ToolButtonIconOnly ); page_vlay->addWidget( mEditToolBar, 0 ); mFormatToolBar = new KToolBar( this ); mFormatToolBar->setToolButtonStyle( Qt::ToolButtonIconOnly ); page_vlay->addWidget( mFormatToolBar, 1 ); mTextEdit = new KPIMTextEdit::TextEdit( this ); page_vlay->addWidget( mTextEdit, 2 ); mTextEdit->setWhatsThis( i18n("Use this field to enter an arbitrary static signature.")); // exclude SupportToPlainText. mTextEdit->setRichTextSupport( KRichTextWidget::FullTextFormattingSupport | KRichTextWidget::FullListSupport | KRichTextWidget::SupportAlignment | KRichTextWidget::SupportRuleLine | KRichTextWidget::SupportHyperlinks | KRichTextWidget::SupportFormatPainting ); // Fill the toolbars. KActionCollection *actionCollection = new KActionCollection(this); mTextEdit->createActions( actionCollection ); mEditToolBar->addAction( actionCollection->action( "format_text_bold" ) ); mEditToolBar->addAction( actionCollection->action( "format_text_italic" ) ); mEditToolBar->addAction( actionCollection->action( "format_text_underline" ) ); mEditToolBar->addAction( actionCollection->action( "format_text_strikeout" ) ); mEditToolBar->addAction( actionCollection->action( "format_text_foreground_color" ) ); mEditToolBar->addAction( actionCollection->action( "format_text_background_color" ) ); mEditToolBar->addAction( actionCollection->action( "format_font_family" ) ); mEditToolBar->addAction( actionCollection->action( "format_font_size" ) ); mFormatToolBar->addAction( actionCollection->action( "format_list_style" ) ); mFormatToolBar->addAction( actionCollection->action( "format_list_indent_more" ) ); mFormatToolBar->addAction( actionCollection->action( "format_list_indent_less" ) ); mFormatToolBar->addAction( actionCollection->action( "format_list_indent_less" ) ); mFormatToolBar->addSeparator(); mFormatToolBar->addAction( actionCollection->action( "format_align_left" ) ); mFormatToolBar->addAction( actionCollection->action( "format_align_center" ) ); mFormatToolBar->addAction( actionCollection->action( "format_align_right" ) ); mFormatToolBar->addAction( actionCollection->action( "format_align_justify" ) ); mFormatToolBar->addSeparator(); mFormatToolBar->addAction( actionCollection->action( "insert_horizontal_rule" ) ); mFormatToolBar->addAction( actionCollection->action( "manage_link" ) ); mFormatToolBar->addAction( actionCollection->action( "format_painter" ) ); hlay = new QHBoxLayout(); // inherits spacing page_vlay->addLayout( hlay ); mHtmlCheck = new QCheckBox( i18n("&Use HTML"), page ); connect( mHtmlCheck, SIGNAL(clicked()), this, SLOT(slotSetHtml()) ); hlay->addWidget( mHtmlCheck ); d->inlinedHtml = true; widgetStack->setCurrentIndex( 0 ); // since mSourceCombo->currentItem() == 0 // page 1: "signature file" requester, label, "edit file" button: ++pageno; page = new QWidget( widgetStack ); widgetStack->insertWidget( pageno, page ); // force sequential numbers (play safe) page_vlay = new QVBoxLayout( page ); page_vlay->setMargin( 0 ); - page_vlay->setSpacing( KDialog::spacingHint() ); hlay = new QHBoxLayout(); // inherits spacing page_vlay->addLayout( hlay ); mFileRequester = new KUrlRequester( page ); mFileRequester->setWhatsThis( i18n("Use this requester to specify a text file that contains your " "signature. It will be read every time you create a new mail or " "append a new signature.")); label = new QLabel( i18n("S&pecify file:"), page ); label->setBuddy( mFileRequester ); hlay->addWidget( label ); hlay->addWidget( mFileRequester, 1 ); mFileRequester->button()->setAutoDefault( false ); connect( mFileRequester, SIGNAL(textChanged(const QString &)), this, SLOT(slotEnableEditButton(const QString &)) ); mEditButton = new QPushButton( i18n("Edit &File"), page ); mEditButton->setWhatsThis( i18n("Opens the specified file in a text editor.")); connect( mEditButton, SIGNAL(clicked()), SLOT(slotEdit()) ); mEditButton->setAutoDefault( false ); mEditButton->setEnabled( false ); // initially nothing to edit hlay->addWidget( mEditButton ); page_vlay->addStretch( 1 ); // spacer // page 2: "signature command" requester and label: ++pageno; page = new QWidget( widgetStack ); widgetStack->insertWidget( pageno,page ); page_vlay = new QVBoxLayout( page ); page_vlay->setMargin( 0 ); - page_vlay->setSpacing( KDialog::spacingHint() ); hlay = new QHBoxLayout(); // inherits spacing page_vlay->addLayout( hlay ); mCommandEdit = new KLineEdit( page ); mCommandEdit->setCompletionObject( new KShellCompletion() ); mCommandEdit->setAutoDeleteCompletionObject( true ); mCommandEdit->setWhatsThis( i18n("You can add an arbitrary command here, either with or without path " "depending on whether or not the command is in your Path. For every " "new mail, KMail will execute the command and use what it outputs (to " "standard output) as a signature. Usual commands for use with this " "mechanism are \"fortune\" or \"ksig -random\".")); label = new QLabel( i18n("S&pecify command:"), page ); label->setBuddy( mCommandEdit ); hlay->addWidget( label ); hlay->addWidget( mCommandEdit, 1 ); page_vlay->addStretch( 1 ); // spacer } SignatureConfigurator::~SignatureConfigurator() { delete d; } bool SignatureConfigurator::isSignatureEnabled() const { return mEnableCheck->isChecked(); } void SignatureConfigurator::setSignatureEnabled( bool enable ) { mEnableCheck->setChecked( enable ); } Signature::Type SignatureConfigurator::signatureType() const { if ( !isSignatureEnabled() ) return Signature::Disabled; switch ( mSourceCombo->currentIndex() ) { case 0: return Signature::Inlined; case 1: return Signature::FromFile; case 2: return Signature::FromCommand; default: return Signature::Disabled; } } void SignatureConfigurator::setSignatureType( Signature::Type type ) { setSignatureEnabled( type != Signature::Disabled ); int idx = 0; switch( type ) { case Signature::Inlined: idx = 0; break; case Signature::FromFile: idx = 1; break; case Signature::FromCommand: idx = 2; break; default: idx = 0; break; }; mSourceCombo->setCurrentIndex( idx ); } void SignatureConfigurator::setInlineText( const QString & text ) { mTextEdit->setTextOrHtml( text ); } QString SignatureConfigurator::fileURL() const { QString file = mFileRequester->url().path(); // Force the filename to be relative to ~ instead of $PWD depending // on the rest of the code (KRun::run in Edit and KFileItem on save) if ( !file.isEmpty() && QFileInfo( file ).isRelative() ) file = QDir::home().absolutePath() + QDir::separator() + file; return file; } void SignatureConfigurator::setFileURL( const QString & url ) { mFileRequester->setUrl( url ); } QString SignatureConfigurator::commandURL() const { return mCommandEdit->text(); } void SignatureConfigurator::setCommandURL( const QString & url ) { mCommandEdit->setText( url ); } Signature SignatureConfigurator::signature() const { Signature sig; const Signature::Type sigType = signatureType(); switch ( sigType ) { case Signature::Inlined: sig.setInlinedHtml( d->inlinedHtml ); sig.setText( d->inlinedHtml ? asCleanedHTML() : mTextEdit->textOrHtml() ); break; case Signature::FromCommand: sig.setUrl( commandURL(), true ); break; case Signature::FromFile: sig.setUrl( fileURL(), false ); break; case Signature::Disabled: /* do nothing */ break; } sig.setType( sigType ); return sig; } void SignatureConfigurator::setSignature( const Signature & sig ) { setSignatureType( sig.type() ); if ( sig.isInlinedHtml() ) mHtmlCheck->setCheckState( Qt::Checked ); else mHtmlCheck->setCheckState( Qt::Unchecked ); slotSetHtml(); setInlineText( sig.text() ); if ( sig.type() == Signature::FromFile ) setFileURL( sig.url() ); else setFileURL( QString() ); if ( sig.type() == Signature::FromCommand ) setCommandURL( sig.url() ); else setCommandURL( QString() ); } void SignatureConfigurator::slotEnableEditButton( const QString & url ) { mEditButton->setDisabled( url.trimmed().isEmpty() ); } void SignatureConfigurator::slotEdit() { QString url = fileURL(); // slotEnableEditButton should prevent this assert from being hit: assert( !url.isEmpty() ); (void)KRun::runUrl( KUrl( url ), QString::fromLatin1("text/plain"), this ); } QString SignatureConfigurator::asCleanedHTML() const { QString text = mTextEdit->toHtml(); // Beautiful little hack to find the html headers produced by Qt. QTextDocument textDocument; QString html = textDocument.toHtml(); // Now remove each line from the text, the result is clean html. foreach( const QString& line, html.split( '\n' ) ){ text.remove( line + '\n' ); } return text; } // "use HTML"-checkbox (un)checked void SignatureConfigurator::slotSetHtml() { if ( mHtmlCheck->checkState() == Qt::Unchecked ) { mHtmlCheck->setText( i18n("&Use HTML") ); mEditToolBar->setVisible( false ); mEditToolBar->setEnabled( false ); mFormatToolBar->setVisible( false ); mFormatToolBar->setEnabled( false ); mTextEdit->switchToPlainText(); d->inlinedHtml = false; } else { mHtmlCheck->setText( i18n("&Use HTML (disabling removes formatting)") ); d->inlinedHtml = true; mEditToolBar->setVisible( true ); mEditToolBar->setEnabled( true ); mFormatToolBar->setVisible( true ); mFormatToolBar->setEnabled( true ); mTextEdit->enableRichTextMode(); } } } #include "signatureconfigurator.moc" diff --git a/kresources/configdialog.cpp b/kresources/configdialog.cpp index 327eb9db2..f8afad008 100644 --- a/kresources/configdialog.cpp +++ b/kresources/configdialog.cpp @@ -1,171 +1,167 @@ /* This file is part of libkresources. Copyright (c) 2002 Tobias Koenig Copyright (c) 2002 Jan-Pascal van Best Copyright (c) 2003 Cornelius Schumacher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the KDE resource framework and defines the ConfigDialog class. @brief Provides a resource configuration dialog. @author Tobias Koenig @author Jan-Pascal van Best */ #include "configdialog.h" #include #include #include #include #include #include #include #include "factory.h" using namespace KRES; class ConfigDialog::Private { public: ConfigWidget *mConfigWidget; Resource *mResource; KLineEdit *mName; QCheckBox *mReadOnly; }; ConfigDialog::ConfigDialog( QWidget *parent, const QString &resourceFamily, Resource *resource ) : KDialog( parent ), d( new Private ) { setModal( true ); setCaption( i18nc( "@title:window", "Resource Configuration" ) ); setButtons( Ok | Cancel ); setDefaultButton( Ok ); showButtonSeparator( false ); d->mResource = resource; Factory *factory = Factory::self( resourceFamily ); QFrame *main = new QFrame( this ); setMainWidget( main ); QVBoxLayout *mainLayout = new QVBoxLayout( main ); - mainLayout->setSpacing( spacingHint() ); mainLayout->setMargin( 0 ); QGroupBox *generalGroupBox = new QGroupBox( main ); QGridLayout *gbLayout = new QGridLayout; - gbLayout->setSpacing( spacingHint() ); generalGroupBox->setLayout( gbLayout ); generalGroupBox->setTitle( i18nc( "@title:group", "General Settings" ) ); gbLayout->addWidget( new QLabel( i18nc( "@label resource name", "Name:" ), generalGroupBox ), 0, 0 ); d->mName = new KLineEdit(); gbLayout->addWidget( d->mName, 0, 1 ); d->mReadOnly = new QCheckBox( i18nc( "@option:check if resource is read-only", "Read-only" ), generalGroupBox ); gbLayout->addWidget( d->mReadOnly, 1, 0, 1, 2 ); d->mName->setText( d->mResource->resourceName() ); d->mReadOnly->setChecked( d->mResource->readOnly() ); mainLayout->addWidget( generalGroupBox ); QGroupBox *resourceGroupBox = new QGroupBox( main ); QGridLayout *resourceLayout = new QGridLayout; - resourceLayout->setSpacing( spacingHint() ); - resourceLayout->setMargin( marginHint() ); resourceGroupBox->setLayout( resourceLayout ); resourceGroupBox->setTitle( i18nc( "@title:group", "%1 Resource Settings", factory->typeName( resource->type() ) ) ); mainLayout->addWidget( resourceGroupBox ); mainLayout->addStretch(); d->mConfigWidget = factory->configWidget( resource->type(), resourceGroupBox ); if ( d->mConfigWidget ) { resourceLayout->addWidget( d->mConfigWidget ); d->mConfigWidget->setInEditMode( false ); d->mConfigWidget->loadSettings( d->mResource ); d->mConfigWidget->show(); connect( d->mConfigWidget, SIGNAL( setReadOnly( bool ) ), SLOT( setReadOnly( bool ) ) ); } connect( d->mName, SIGNAL( textChanged(const QString &) ), SLOT( slotNameChanged(const QString &) ) ); slotNameChanged( d->mName->text() ); setMinimumSize( sizeHint() ); } ConfigDialog::~ConfigDialog() { delete d; } void ConfigDialog::setInEditMode( bool value ) { if ( d->mConfigWidget ) { d->mConfigWidget->setInEditMode( value ); } } void ConfigDialog::slotNameChanged( const QString &text ) { enableButtonOk( !text.isEmpty() ); } void ConfigDialog::setReadOnly( bool value ) { d->mReadOnly->setChecked( value ); } void ConfigDialog::accept() { if ( d->mName->text().isEmpty() ) { KMessageBox::sorry( this, i18nc( "@info", "Please enter a resource name." ) ); return; } d->mResource->setResourceName( d->mName->text() ); d->mResource->setReadOnly( d->mReadOnly->isChecked() ); if ( d->mConfigWidget ) { // First save generic information // Also save setting of specific resource type d->mConfigWidget->saveSettings( d->mResource ); } KDialog::accept(); } #include "configdialog.moc" diff --git a/kresources/configpage.cpp b/kresources/configpage.cpp index 8f2c1846b..98c72ff4b 100644 --- a/kresources/configpage.cpp +++ b/kresources/configpage.cpp @@ -1,615 +1,614 @@ /* This file is part of libkresources. Copyright (c) 2002 Tobias Koenig Copyright (c) 2002 Jan-Pascal van Best Copyright (c) 2003 Cornelius Schumacher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the KDE resource framework and defines the ConfigPage class. @brief A resource configuration page. @author Tobias Koenig @author Jan-Pascal van Best @author Cornelius Schumacher */ #include "configpage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "resource.h" #include "configdialog.h" namespace KRES { class ResourcePageInfo::Private { }; ResourcePageInfo::ResourcePageInfo() : d( new KRES::ResourcePageInfo::Private ) { mManager = 0; mConfig = 0; } ResourcePageInfo::~ResourcePageInfo() { //delete mManager; mManager = 0; //delete mConfig; mConfig = 0; delete d; } class ConfigViewItem : public QTreeWidgetItem { public: ConfigViewItem( QTreeWidget *parent, Resource *resource ) : QTreeWidgetItem( parent ), mResource( resource ), mIsStandard( false ) { updateItem(); } void setStandard( bool value ) { setText( 2, ( value ? i18nc( "yes, a standard resource", "Yes" ) : QString() ) ); mIsStandard = value; } bool standard() const { return mIsStandard; } bool readOnly() const { return mResource->readOnly(); } Resource *resource() { return mResource; } void updateItem() { setCheckState( 0, mResource->isActive() ? Qt::Checked : Qt::Unchecked ); setText( 0, mResource->resourceName() ); setText( 1, mResource->type() ); setText( 2, mIsStandard ? i18nc( "yes, a standard resource", "Yes" ) : QString() ); } bool isOn() { return checkState( 0 ) == Qt::Checked; } private: Resource *mResource; bool mIsStandard; }; class ConfigPage::Private { public: void loadManager( const QString &family, ConfigPage *page ); void saveResourceSettings( ConfigPage *page ); Manager *mCurrentManager; KConfig *mCurrentConfig; KConfigGroup *mConfigGroup; QString mFamily; QStringList mFamilyMap; QList > mInfoMap; KComboBox *mFamilyCombo; QTreeWidget *mListView; QPushButton *mAddButton; QPushButton *mRemoveButton; QPushButton *mEditButton; QPushButton *mStandardButton; QTreeWidgetItem *mLastItem; }; ConfigPage::ConfigPage( QWidget *parent ) : QWidget( parent ), d( new KRES::ConfigPage::Private ) { setWindowTitle( i18n( "Resource Configuration" ) ); QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setMargin( 0 ); QGroupBox *groupBox = new QGroupBox( i18n( "Resources" ), this ); QGridLayout *groupBoxLayout = new QGridLayout(); groupBox->setLayout( groupBoxLayout ); - groupBoxLayout->setSpacing( 6 ); - groupBoxLayout->setMargin( 11 ); d->mFamilyCombo = new KComboBox( false, groupBox ); groupBoxLayout->addWidget( d->mFamilyCombo, 0, 0, 1, 2 ); d->mCurrentManager = 0; d->mCurrentConfig = 0; d->mListView = new QTreeWidget( groupBox ); d->mListView->setColumnCount( 3 ); QStringList headerLabels; headerLabels << i18nc( "@title:column resource name", "Name" ) << i18nc( "@title:column resource type", "Type" ) << i18nc( "@title:column a standard resource?", "Standard" ); d->mListView->setHeaderItem( new QTreeWidgetItem( headerLabels ) ); groupBoxLayout->addWidget( d->mListView, 1, 0 ); connect( d->mListView, SIGNAL( itemDoubleClicked( QTreeWidgetItem *, int ) ), this, SLOT( slotEdit() ) ); KDialogButtonBox *buttonBox = new KDialogButtonBox( groupBox, Qt::Vertical ); d->mAddButton = buttonBox->addButton( i18n( "&Add..." ), KDialogButtonBox::ActionRole, this, SLOT(slotAdd()) ); d->mRemoveButton = buttonBox->addButton( i18n( "&Remove" ), KDialogButtonBox::ActionRole, this, SLOT(slotRemove()) ); d->mRemoveButton->setEnabled( false ); d->mEditButton = buttonBox->addButton( i18n( "&Edit..." ), KDialogButtonBox::ActionRole, this, SLOT(slotEdit()) ); d->mEditButton->setEnabled( false ); d->mStandardButton = buttonBox->addButton( i18n( "&Use as Standard" ), KDialogButtonBox::ActionRole, this, SLOT(slotStandard()) ); d->mStandardButton->setEnabled( false ); buttonBox->layout(); groupBoxLayout->addWidget( buttonBox, 1, 1 ); mainLayout->addWidget( groupBox ); connect( d->mFamilyCombo, SIGNAL( activated( int ) ), SLOT( slotFamilyChanged( int ) ) ); connect( d->mListView, SIGNAL( itemSelectionChanged() ), SLOT( slotSelectionChanged() ) ); connect( d->mListView, SIGNAL( itemClicked( QTreeWidgetItem *, int ) ), SLOT( slotItemClicked( QTreeWidgetItem * ) ) ); d->mLastItem = 0; d->mConfigGroup = new KConfigGroup( new KConfig( "kcmkresourcesrc" ), "General" ); load(); } ConfigPage::~ConfigPage() { QList >::Iterator it; for ( it = d->mInfoMap.begin(); it != d->mInfoMap.end(); ++it ) { (*it)->mManager->removeObserver( this ); } d->mConfigGroup->writeEntry( "CurrentFamily", d->mFamilyCombo->currentIndex() ); delete d->mConfigGroup->config(); delete d->mConfigGroup; d->mConfigGroup = 0; delete d; } void ConfigPage::load() { kDebug(); d->mListView->clear(); d->mFamilyMap.clear(); d->mInfoMap.clear(); QStringList familyDisplayNames; // KDE-3.3 compatibility code: get families from the plugins QStringList compatFamilyNames; const KService::List plugins = KServiceTypeTrader::self()->query( "KResources/Plugin" ); KService::List::ConstIterator it = plugins.begin(); KService::List::ConstIterator end = plugins.end(); for ( ; it != end; ++it ) { const QString family = (*it)->property( "X-KDE-ResourceFamily" ).toString(); if ( compatFamilyNames.indexOf( family ) == -1 ) { compatFamilyNames.append( family ); } } const KService::List managers = KServiceTypeTrader::self()->query( "KResources/Manager" ); KService::List::ConstIterator m_it; for ( m_it = managers.begin(); m_it != managers.end(); ++m_it ) { QString displayName = (*m_it)->property( "Name" ).toString(); familyDisplayNames.append( displayName ); QString family = (*m_it)->property( "X-KDE-ResourceFamily" ).toString(); if ( !family.isEmpty() ) { compatFamilyNames.removeAll( family ); d->mFamilyMap.append( family ); d->loadManager( family, this ); } } // Rest of the kde-3.3 compat code QStringList::ConstIterator cfit = compatFamilyNames.constBegin(); for ( ; cfit != compatFamilyNames.constEnd(); ++cfit ) { d->mFamilyMap.append( *cfit ); familyDisplayNames.append( *cfit ); d->loadManager( *cfit, this ); } d->mCurrentManager = 0; d->mFamilyCombo->clear(); d->mFamilyCombo->insertItems( 0, familyDisplayNames ); int currentFamily = d->mConfigGroup->readEntry( "CurrentFamily", 0 ); d->mFamilyCombo->setCurrentIndex( currentFamily ); slotFamilyChanged( currentFamily ); emit changed( false ); } void ConfigPage::Private::loadManager( const QString &family, ConfigPage *page ) { mCurrentManager = new Manager( family ); if ( mCurrentManager ) { mCurrentManager->addObserver( page ); ResourcePageInfo *info = new ResourcePageInfo; info->mManager = mCurrentManager; info->mConfig = new KConfig( KRES::ManagerImpl::defaultConfigFile( family ) ); info->mManager->readConfig( info->mConfig ); mInfoMap.append( KSharedPtr( info ) ); } } void ConfigPage::save() { d->saveResourceSettings( this ); QList >::Iterator it; for ( it = d->mInfoMap.begin(); it != d->mInfoMap.end(); ++it ) { (*it)->mManager->writeConfig( (*it)->mConfig ); } emit changed( false ); } void ConfigPage::defaults() { } void ConfigPage::slotFamilyChanged( int pos ) { if ( pos < 0 || pos >= (int)d->mFamilyMap.count() ) { return; } d->saveResourceSettings( this ); d->mFamily = d->mFamilyMap[ pos ]; d->mCurrentManager = d->mInfoMap[ pos ]->mManager; d->mCurrentConfig = d->mInfoMap[ pos ]->mConfig; if ( !d->mCurrentManager ) { kDebug() << "ERROR: cannot create ResourceManager( mFamily )"; } d->mListView->clear(); if ( d->mCurrentManager->isEmpty() ) { defaults(); } Resource *standardResource = d->mCurrentManager->standardResource(); Manager::Iterator it; for ( it = d->mCurrentManager->begin(); it != d->mCurrentManager->end(); ++it ) { ConfigViewItem *item = new ConfigViewItem( d->mListView, *it ); if ( *it == standardResource ) { item->setStandard( true ); } } if ( d->mListView->topLevelItemCount() == 0 ) { defaults(); emit changed( true ); d->mCurrentManager->writeConfig( d->mCurrentConfig ); } else { if ( !standardResource ) { KMessageBox::sorry( this, i18n( "There is no standard resource. Please select one." ) ); } emit changed( false ); } } void ConfigPage::slotAdd() { if ( !d->mCurrentManager ) { return; } QStringList types = d->mCurrentManager->resourceTypeNames(); QStringList descs = d->mCurrentManager->resourceTypeDescriptions(); bool ok = false; QString desc = KInputDialog::getItem( i18n( "Resource Configuration" ), i18n( "Please select type of the new resource:" ), descs, 0, false, &ok, this ); if ( !ok ) { return; } QString type = types[ descs.indexOf( desc ) ]; // Create new resource Resource *resource = d->mCurrentManager->createResource( type ); if ( !resource ) { KMessageBox::error( this, i18n( "Unable to create resource of type '%1'.", type ) ); return; } resource->setResourceName( type + "-resource" ); ConfigDialog dlg( this, d->mFamily, resource ); if ( dlg.exec() ) { d->mCurrentManager->add( resource ); ConfigViewItem *item = new ConfigViewItem( d->mListView, resource ); d->mLastItem = item; // if there are only read-only resources we'll set this resource // as standard resource if ( !resource->readOnly() ) { bool onlyReadOnly = true; for ( int i = 0; i < d->mListView->topLevelItemCount(); ++i ) { ConfigViewItem *confIt = static_cast( d->mListView->topLevelItem( i ) ); if ( !confIt->readOnly() && confIt != item ) { onlyReadOnly = false; } } if ( onlyReadOnly ) { item->setStandard( true ); } } emit changed( true ); } else { delete resource; resource = 0; } } void ConfigPage::slotRemove() { if ( !d->mCurrentManager ) { return; } QTreeWidgetItem *item = d->mListView->currentItem(); ConfigViewItem *confItem = static_cast( item ); if ( !confItem ) { return; } if ( confItem->standard() ) { KMessageBox::sorry( this, i18n( "You cannot remove your standard resource. " "Please select a new standard resource first." ) ); return; } d->mCurrentManager->remove( confItem->resource() ); if ( item == d->mLastItem ) { d->mLastItem = 0; } d->mListView->takeTopLevelItem( d->mListView->indexOfTopLevelItem( item ) ); delete item; emit changed( true ); } void ConfigPage::slotEdit() { if ( !d->mCurrentManager ) { return; } QTreeWidgetItem *item = d->mListView->currentItem(); ConfigViewItem *configItem = static_cast( item ); if ( !configItem ) { return; } Resource *resource = configItem->resource(); ConfigDialog dlg( this, d->mFamily, resource ); if ( dlg.exec() ) { configItem->setText( 0, resource->resourceName() ); configItem->setText( 1, resource->type() ); if ( configItem->standard() && configItem->readOnly() ) { KMessageBox::sorry( this, i18n( "You cannot use a read-only resource as standard." ) ); configItem->setStandard( false ); } d->mCurrentManager->change( resource ); emit changed( true ); } } void ConfigPage::slotStandard() { if ( !d->mCurrentManager ) { return; } ConfigViewItem *item = static_cast( d->mListView->currentItem() ); if ( !item ) { return; } if ( item->readOnly() ) { KMessageBox::sorry( this, i18n( "You cannot use a read-only resource as standard." ) ); return; } if ( !item->isOn() ) { KMessageBox::sorry( this, i18n( "You cannot use an inactive resource as standard." ) ); return; } for ( int i = 0; i < d->mListView->topLevelItemCount(); ++i ) { ConfigViewItem *configItem = static_cast( d->mListView->topLevelItem( i ) ); if ( configItem->standard() ) { configItem->setStandard( false ); } } item->setStandard( true ); d->mCurrentManager->setStandardResource( item->resource() ); emit changed( true ); } void ConfigPage::slotSelectionChanged() { bool state = ( d->mListView->currentItem() != 0 ); d->mRemoveButton->setEnabled( state ); d->mEditButton->setEnabled( state ); d->mStandardButton->setEnabled( state ); } void ConfigPage::resourceAdded( Resource *resource ) { kDebug() << resource->resourceName(); ConfigViewItem *item = new ConfigViewItem( d->mListView, resource ); item->setCheckState( 0, resource->isActive()? Qt::Checked : Qt::Unchecked ); d->mLastItem = item; emit changed( true ); } void ConfigPage::resourceModified( Resource *resource ) { kDebug() << resource->resourceName(); ConfigViewItem *item = findItem( resource ); if ( !item ) { return; } // TODO: Reread resource config. Otherwise we won't see the modification. item->updateItem(); } void ConfigPage::resourceDeleted( Resource *resource ) { kDebug() << resource->resourceName(); ConfigViewItem *item = findItem( resource ); if ( !item ) { return; } delete item; } ConfigViewItem *ConfigPage::findItem( Resource *resource ) { for ( int i = 0; i < d->mListView->topLevelItemCount(); ++i ) { ConfigViewItem *item = static_cast( d->mListView->topLevelItem( i ) ); if ( item->resource() == resource ) { return item; } } return 0; } void ConfigPage::slotItemClicked( QTreeWidgetItem *item ) { ConfigViewItem *configItem = static_cast( item ); if ( !configItem ) { return; } if ( configItem->standard() && !configItem->isOn() ) { KMessageBox::sorry( this, i18n( "You cannot deactivate the standard resource. " "Choose another standard resource first." ) ); configItem->setCheckState( 0, Qt::Checked ); return; } if ( configItem->isOn() != configItem->resource()->isActive() ) { emit changed( true ); } } void ConfigPage::Private::saveResourceSettings( ConfigPage *page ) { if ( mCurrentManager ) { for ( int i = 0; i < mListView->topLevelItemCount(); ++i ) { ConfigViewItem *configItem = static_cast( mListView->topLevelItem( i ) ); // check if standard resource if ( configItem->standard() && !configItem->readOnly() && configItem->isOn() ) { mCurrentManager->setStandardResource( configItem->resource() ); } // check if active or passive resource configItem->resource()->setActive( configItem->isOn() ); } mCurrentManager->writeConfig( mCurrentConfig ); if ( !mCurrentManager->standardResource() ) { KMessageBox::sorry( page, i18n( "There is no valid standard resource. " "Please select one which is neither read-only nor inactive." ) ); } } } } #include "configpage.moc" diff --git a/kresources/kcmkresources.cpp b/kresources/kcmkresources.cpp index 66a7f4205..402c51c8f 100644 --- a/kresources/kcmkresources.cpp +++ b/kresources/kcmkresources.cpp @@ -1,70 +1,71 @@ /* This file is part of libkresources. Copyright (c) 2003 Tobias Koenig 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 "kcmkresources.h" #include #include #include #include #include #include "configpage.h" K_PLUGIN_FACTORY( ResourcesFactory, registerPlugin(); ) K_EXPORT_PLUGIN( ResourcesFactory( "kcmkresources" ) ) KCMKResources::KCMKResources( QWidget *parent, const QVariantList &l ) : KCModule( ResourcesFactory::componentData(), parent, QVariantList() ) { Q_UNUSED( l ); KGlobal::locale()->insertCatalog( "libkresources" ); QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); mConfigPage = new KRES::ConfigPage( this ); layout->addWidget( mConfigPage ); connect( mConfigPage, SIGNAL( changed( bool ) ), SIGNAL( changed( bool ) ) ); setButtons( Help | Apply ); KAboutData *about = new KAboutData( I18N_NOOP( "kcmkresources" ), 0, ki18n( "KDE Resources configuration module" ), 0, KLocalizedString(), KAboutData::License_GPL, ki18n( "(c) 2003 Tobias Koenig" ) ); about->addAuthor( ki18n( "Tobias Koenig" ), KLocalizedString(), "tokoe@kde.org" ); setAboutData( about ); } void KCMKResources::load() { mConfigPage->load(); } void KCMKResources::save() { mConfigPage->save(); } void KCMKResources::defaults() { mConfigPage->defaults(); } #include "kcmkresources.moc" diff --git a/kresources/selectdialog.cpp b/kresources/selectdialog.cpp index 18353ee62..8803b202c 100644 --- a/kresources/selectdialog.cpp +++ b/kresources/selectdialog.cpp @@ -1,138 +1,135 @@ /* This file is part of libkresources. Copyright (c) 2002 Tobias Koenig Copyright (c) 2002 Jan-Pascal van Best Copyright (c) 2003 Cornelius Schumacher 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 "selectdialog.h" #include #include #include #include #include #include "resource.h" using namespace KRES; class SelectDialog::SelectDialogPrivate { public: QListWidget *mResourceId; QMap mResourceMap; }; SelectDialog::SelectDialog( QList list, QWidget *parent ) : KDialog( parent ), d( new SelectDialogPrivate ) { setModal(true); setCaption( i18n( "Resource Selection" ) ); resize( 300, 200 ); setButtons( Ok|Cancel ); setDefaultButton( Ok ); QWidget *widget = new QWidget( this ); setMainWidget( widget ); QVBoxLayout *mainLayout = new QVBoxLayout( widget ); mainLayout->setMargin( 0 ); - mainLayout->setSpacing( spacingHint() ); QGroupBox *groupBox = new QGroupBox( widget ); QGridLayout *grid = new QGridLayout; - grid->setMargin( marginHint() ); - grid->setSpacing( spacingHint() ); groupBox->setLayout( grid ); groupBox->setTitle( i18n( "Resources" ) ); d->mResourceId = new QListWidget( groupBox ); grid->addWidget( d->mResourceId, 0, 0 ); mainLayout->addWidget( groupBox ); // setup listbox uint counter = 0; for ( int i = 0; i < list.count(); ++i ) { Resource *resource = list.at( i ); if ( resource && !resource->readOnly() ) { d->mResourceMap.insert( counter, resource ); d->mResourceId->addItem( resource->resourceName() ); counter++; } } d->mResourceId->setCurrentRow( 0 ); connect( d->mResourceId, SIGNAL( itemActivated(QListWidgetItem*)), SLOT(accept()) ); } SelectDialog::~SelectDialog() { delete d; } Resource *SelectDialog::resource() { if ( d->mResourceId->currentRow() != -1 ) { return d->mResourceMap[ d->mResourceId->currentRow() ]; } else { return 0; } } Resource *SelectDialog::getResource( QList list, QWidget *parent ) { if ( list.count() == 0 ) { KMessageBox::error( parent, i18n( "There is no resource available." ) ); return 0; } if ( list.count() == 1 ) { return list.first(); } // the following lines will return a writeable resource if only _one_ // writeable resource exists Resource *found = 0; for ( int i=0; i< list.size(); ++i ) { if ( !list.at(i)->readOnly() ) { if ( found ) { found = 0; break; } } else { found = list.at(i); } } if ( found ) { return found; } SelectDialog dlg( list, parent ); if ( dlg.exec() == KDialog::Accepted ) { return dlg.resource(); } else { return 0; } } diff --git a/mailtransport/transportjob.h b/mailtransport/transportjob.h index c4aefd029..4afcc4c76 100644 --- a/mailtransport/transportjob.h +++ b/mailtransport/transportjob.h @@ -1,142 +1,146 @@ /* Copyright (c) 2007 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. */ #ifndef MAILTRANSPORT_TRANSPORTJOB_H #define MAILTRANSPORT_TRANSPORTJOB_H #include #include #include class QBuffer; namespace MailTransport { class Transport; /** Abstract base class for all mail transport jobs. This is a job that is supposed to send exactly one mail. @deprecated Use MessageQueueJob for sending e-mail. */ class MAILTRANSPORT_EXPORT_DEPRECATED TransportJob : public KCompositeJob { friend class TransportManager; public: /** Deletes this transport job. */ virtual ~TransportJob(); /** Sets the sender of the mail. + @p sender must be the plain email address, not including display name. */ void setSender( const QString &sender ); /** Sets the "To" receiver(s) of the mail. + @p to must be the plain email address(es), not including display name. */ void setTo( const QStringList &to ); /** Sets the "Cc" receiver(s) of the mail. + @p cc must be the plain email address(es), not including display name. */ void setCc( const QStringList &cc ); /** Sets the "Bcc" receiver(s) of the mail. + @p bcc must be the plain email address(es), not including display name. */ void setBcc( const QStringList &bcc ); /** Sets the content of the mail. */ void setData( const QByteArray &data ); /** Starts this job. It is recommended to not call this method directly but use TransportManager::schedule() to execute the job instead. @see TransportManager::schedule() */ virtual void start(); /** Returns the Transport object containing the mail transport settings. */ Transport *transport() const; protected: /** Creates a new mail transport job. @param transport The transport configuration. This must be a deep copy of a Transport object, the job takes the ownership of this object. @param parent The parent object. @see TransportManager::createTransportJob() */ explicit TransportJob( Transport *transport, QObject *parent = 0 ); /** Returns the sender of the mail. */ QString sender() const; /** Returns the "To" receiver(s) of the mail. */ QStringList to() const; /** Returns the "Cc" receiver(s) of the mail. */ QStringList cc() const; /** Returns the "Bcc" receiver(s) of the mail. */ QStringList bcc() const; /** Returns the data of the mail. */ QByteArray data() const; /** Returns a QBuffer opened on the message data. This is useful for processing the data in smaller chunks. */ QBuffer *buffer(); /** Do the actual work, implement in your subclass. */ virtual void doStart() = 0; private: class Private; Private *const d; }; } // namespace MailTransport #endif // MAILTRANSPORT_TRANSPORTJOB_H