diff --git a/akonadi/collection.h b/akonadi/collection.h index 433daed24..8d010f5a2 100644 --- a/akonadi/collection.h +++ b/akonadi/collection.h @@ -1,243 +1,244 @@ /* 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_COLLECTION_H #define AKONADI_COLLECTION_H #include "akonadi_export.h" #include #include #include class KUrl; namespace Akonadi { class CachePolicy; class CollectionPrivate; class CollectionStatistics; /** * @short Represents a collection of PIM items. * * This class represents a collection of PIM items, such as a folder on a mail- or * groupware-server. * * Collections are hierarchical, i.e., they may have a parent collection. * * @code * * using namespace Akonadi; * * // fetching all collections recursive, starting at the root collection * CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); * if ( job->exec() ) { * Collection::List collections = job->collections(); * foreach( const Collection &collection, collections ) { * qDebug() << "Name:" << collection.name(); * } * } * * @endcode * * @author Volker Krause * * @see \ref akonadi_concepts_collections "Akonadi Collection Concept" */ class AKONADI_EXPORT Collection : public Entity { public: /** * Describes a list of collections. */ typedef QList List; /** * Describes rights of a collection. */ enum Right { ReadOnly = 0x0, ///< Can only read items or subcollection of this collection CanChangeItem = 0x1, ///< Can change items in this collection CanCreateItem = 0x2, ///< Can create new items in this collection CanDeleteItem = 0x4, ///< Can delete items in this collection CanChangeCollection = 0x8, ///< Can change subcollections in this collection CanCreateCollection = 0x10, ///< Can create new subcollections in this collection CanDeleteCollection = 0x20, ///< Can delete subcollections in this collection AllRights = (CanChangeItem | CanCreateItem | CanDeleteItem | CanChangeCollection | CanCreateCollection | CanDeleteCollection) ///< Has all rights on this collection }; Q_DECLARE_FLAGS(Rights, Right) /** * Creates an invalid collection. */ Collection(); /** * Create a new collection. * * @param id The unique identifier of the collection. */ explicit Collection( Id id ); /** * Destroys the collection. */ ~Collection(); /** * Creates a collection from an @p other collection. */ Collection( const Collection &other ); /** * Creates a collection from the given @p url. */ static Collection fromUrl( const KUrl &url ); /** * Returns the i18n'ed name of the collection. */ QString name() const; /** * Sets the i18n'ed name of the collection. * * @param name The new collection name. */ void setName( const QString &name ); /** * Returns the rights the user has on the collection. */ Rights rights() const; /** * Sets the @p rights the user has on the collection. */ void setRights( Rights rights ); /** * Returns a list of possible content mimetypes, * e.g. message/rfc822, x-akonadi/collection for a mail folder that * supports sub-folders. */ QStringList contentMimeTypes() const; /** * Sets the list of possible content mime @p types. */ void setContentMimeTypes( const QStringList &types ); /** * Returns the identifier of the parent collection. * @deprecated Use parentCollection() */ KDE_DEPRECATED Id parent() const; /** * Sets the identifier of the @p parent collection. * @deprecated Use setParentCollection() */ KDE_DEPRECATED void setParent( Id parent ); /** * Sets the parent @p collection. * @deprecated Use setParentCollection() */ KDE_DEPRECATED void setParent( const Collection &collection ); /** * Returns the parent remote identifier. * @note This usually returns nothing for collections retrieved from the backend. * @deprecated Use parentCollection() */ KDE_DEPRECATED QString parentRemoteId() const; /** * Sets the parent's remote @p identifier. * @deprecated Use setParentCollection() */ KDE_DEPRECATED void setParentRemoteId( const QString &identifier ); /** * Returns the root collection. */ static Collection root(); /** * Returns the mimetype used for collections. */ static QString mimeType(); /** * Returns the identifier of the resource owning the collection. */ QString resource() const; /** * Sets the @p identifier of the resource owning the collection. */ void setResource( const QString &identifier ); /** * Returns the cache policy of the collection. */ CachePolicy cachePolicy() const; /** * Sets the cache @p policy of the collection. */ void setCachePolicy( const CachePolicy &policy ); /** * Returns the collection statistics of the collection. */ CollectionStatistics statistics() const; /** * Sets the collection @p statistics for the collection. */ void setStatistics( const CollectionStatistics &statistics ); /** * Returns the collection url */ KUrl url() const; private: AKONADI_DECLARE_PRIVATE( Collection ) friend class CollectionFetchJob; friend class CollectionModifyJob; }; } AKONADI_EXPORT uint qHash( const Akonadi::Collection &collection ); /** * Allows to output a collection for debugging purposes. */ AKONADI_EXPORT QDebug operator<<( QDebug d, const Akonadi::Collection &collection ); Q_DECLARE_METATYPE(Akonadi::Collection) Q_DECLARE_METATYPE(Akonadi::Collection::List) +Q_DECLARE_OPERATORS_FOR_FLAGS( Akonadi::Collection::Rights ) #endif diff --git a/akonadi/collectionfetchjob.cpp b/akonadi/collectionfetchjob.cpp index 1dcbb94c0..e1d290986 100644 --- a/akonadi/collectionfetchjob.cpp +++ b/akonadi/collectionfetchjob.cpp @@ -1,262 +1,263 @@ /* 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. */ #include "collectionfetchjob.h" #include "imapparser_p.h" #include "job_p.h" #include "protocol_p.h" #include "protocolhelper_p.h" #include "entity_p.h" #include "collectionfetchscope.h" #include #include #include #include using namespace Akonadi; class Akonadi::CollectionFetchJobPrivate : public JobPrivate { public: CollectionFetchJobPrivate( CollectionFetchJob *parent ) : JobPrivate( parent ) { } Q_DECLARE_PUBLIC( CollectionFetchJob ) CollectionFetchJob::Type mType; Collection mBase; Collection::List mBaseList; Collection::List mCollections; CollectionFetchScope mScope; Collection::List mPendingCollections; QTimer *mEmitTimer; void timeout() { Q_Q( CollectionFetchJob ); mEmitTimer->stop(); // in case we are called by result() if ( !mPendingCollections.isEmpty() ) { emit q->collectionsReceived( mPendingCollections ); mPendingCollections.clear(); } } }; CollectionFetchJob::CollectionFetchJob( const Collection &collection, Type type, QObject *parent ) : Job( new CollectionFetchJobPrivate( this ), parent ) { Q_D( CollectionFetchJob ); d->mBase = collection; d->mType = type; d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); } CollectionFetchJob::CollectionFetchJob( const Collection::List & cols, QObject * parent ) : Job( new CollectionFetchJobPrivate( this ), parent ) { Q_D( CollectionFetchJob ); Q_ASSERT( !cols.isEmpty() ); if ( cols.size() == 1 ) { d->mBase = cols.first(); d->mType = CollectionFetchJob::Base; } else { d->mBaseList = cols; } d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); } CollectionFetchJob::~CollectionFetchJob() { } Collection::List CollectionFetchJob::collections() const { Q_D( const CollectionFetchJob ); return d->mCollections; } void CollectionFetchJob::doStart() { Q_D( CollectionFetchJob ); if ( !d->mBaseList.isEmpty() ) { foreach ( const Collection &col, d->mBaseList ) { - new CollectionFetchJob( col, CollectionFetchJob::Base, this ); + CollectionFetchJob *subJob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); + subJob->setFetchScope( fetchScope() ); } return; } if ( !d->mBase.isValid() && d->mBase.remoteId().isEmpty() ) { setError( Unknown ); setErrorText( QLatin1String( "Invalid collection given." ) ); emitResult(); return; } QByteArray command = d->newTag(); if ( !d->mBase.isValid() ) command += " " AKONADI_CMD_RID; if ( d->mScope.includeUnubscribed() ) command += " LIST "; else command += " LSUB "; if ( d->mBase.isValid() ) command += QByteArray::number( d->mBase.id() ); else command += ImapParser::quote( d->mBase.remoteId().toUtf8() ); command += ' '; switch ( d->mType ) { case Base: command += "0 ("; break; case FirstLevel: command += "1 ("; break; case Recursive: command += "INF ("; break; default: Q_ASSERT( false ); } QList filter; if ( !d->mScope.resource().isEmpty() ) { filter.append( "RESOURCE" ); filter.append( d->mScope.resource().toUtf8() ); } if ( !d->mScope.contentMimeTypes().isEmpty() ) { filter.append( "MIMETYPE" ); QList mts; foreach ( const QString &mt, d->mScope.contentMimeTypes() ) mts.append( mt.toUtf8() ); filter.append( '(' + ImapParser::join( mts, " " ) + ')' ); } QList options; if ( d->mScope.includeStatistics() ) { options.append( "STATISTICS" ); options.append( "true" ); } if ( d->mScope.ancestorRetrieval() != CollectionFetchScope::None ) { options.append( "ANCESTORS" ); switch ( d->mScope.ancestorRetrieval() ) { case CollectionFetchScope::None: options.append( "0" ); break; case CollectionFetchScope::Parent: options.append( "1" ); break; case CollectionFetchScope::All: options.append( "INF" ); break; default: Q_ASSERT( false ); } } command += ImapParser::join( filter, " " ) + ") (" + ImapParser::join( options, " " ) + ")\n"; d->writeData( command ); } void CollectionFetchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data ) { Q_D( CollectionFetchJob ); if ( tag == "*" ) { Collection collection; ProtocolHelper::parseCollection( data, collection ); if ( !collection.isValid() ) return; collection.d_ptr->resetChangeLog(); d->mCollections.append( collection ); d->mPendingCollections.append( collection ); if ( !d->mEmitTimer->isActive() ) d->mEmitTimer->start(); return; } kDebug() << "Unhandled server response" << tag << data; } void CollectionFetchJob::setResource(const QString & resource) { Q_D( CollectionFetchJob ); d->mScope.setResource( resource ); } void CollectionFetchJob::slotResult(KJob * job) { Q_D( CollectionFetchJob ); CollectionFetchJob *list = dynamic_cast( job ); Q_ASSERT( job ); d->mCollections += list->collections(); Job::slotResult( job ); if ( !job->error() && !hasSubjobs() ) emitResult(); } void CollectionFetchJob::includeUnsubscribed(bool include) { Q_D( CollectionFetchJob ); d->mScope.setIncludeUnsubscribed( include ); } void CollectionFetchJob::includeStatistics(bool include) { Q_D( CollectionFetchJob ); d->mScope.setIncludeStatistics( include ); } void CollectionFetchJob::setFetchScope( const CollectionFetchScope &scope ) { Q_D( CollectionFetchJob ); d->mScope = scope; } CollectionFetchScope& CollectionFetchJob::fetchScope() { Q_D( CollectionFetchJob ); return d->mScope; } #include "collectionfetchjob.moc" diff --git a/akonadi/collectionfetchscope.cpp b/akonadi/collectionfetchscope.cpp index b77d382e5..f3626ee33 100644 --- a/akonadi/collectionfetchscope.cpp +++ b/akonadi/collectionfetchscope.cpp @@ -1,132 +1,133 @@ /* Copyright (c) 2008 Kevin Krammer Copyright (c) 2009 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 "collectionfetchscope.h" #include #include namespace Akonadi { class CollectionFetchScopePrivate : public QSharedData { public: CollectionFetchScopePrivate() : ancestorDepth( CollectionFetchScope::None ), unsubscribed( false ), statistics( false ) { } CollectionFetchScopePrivate( const CollectionFetchScopePrivate &other ) : QSharedData( other ) { resource = other.resource; contentMimeTypes = other.contentMimeTypes; + ancestorDepth = other.ancestorDepth; unsubscribed = other.unsubscribed; statistics = other.statistics; } public: QString resource; QStringList contentMimeTypes; CollectionFetchScope::AncestorRetrieval ancestorDepth; bool unsubscribed; bool statistics; }; CollectionFetchScope::CollectionFetchScope() { d = new CollectionFetchScopePrivate(); } CollectionFetchScope::CollectionFetchScope( const CollectionFetchScope &other ) : d( other.d ) { } CollectionFetchScope::~CollectionFetchScope() { } CollectionFetchScope &CollectionFetchScope::operator=( const CollectionFetchScope &other ) { if ( &other != this ) d = other.d; return *this; } bool CollectionFetchScope::isEmpty () const { return d->resource.isEmpty() && d->contentMimeTypes.isEmpty() && !d->statistics && !d->unsubscribed && d->ancestorDepth == None; } bool CollectionFetchScope::includeUnubscribed () const { return d->unsubscribed; } void CollectionFetchScope::setIncludeUnsubscribed (bool include) { d->unsubscribed = include; } bool CollectionFetchScope::includeStatistics () const { return d->statistics; } void CollectionFetchScope::setIncludeStatistics (bool include) { d->statistics = include; } QString CollectionFetchScope::resource () const { return d->resource; } void CollectionFetchScope::setResource (const QString & resource) { d->resource = resource; } QStringList CollectionFetchScope::contentMimeTypes () const { return d->contentMimeTypes; } void CollectionFetchScope::setContentMimeTypes (const QStringList & mimeTypes) { d->contentMimeTypes = mimeTypes; } CollectionFetchScope::AncestorRetrieval CollectionFetchScope::ancestorRetrieval() const { return d->ancestorDepth; } void CollectionFetchScope::setAncestorRetrieval( AncestorRetrieval ancestorDepth ) { d->ancestorDepth = ancestorDepth; } } diff --git a/akonadi/firstrun_p.h b/akonadi/firstrun_p.h index f8d713136..1b3d5ce17 100644 --- a/akonadi/firstrun_p.h +++ b/akonadi/firstrun_p.h @@ -1,95 +1,95 @@ /* Copyright (c) 2008 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_FIRSTRUN_P_H #define AKONADI_FIRSTRUN_P_H #include #include #include class KConfig; class KJob; class KProcess; -class QMetaObject; +struct QMetaObject; namespace Akonadi { /** Takes care of setting up default resource agents when running Akonadi for the first time.

Defining your own default agent setups

To add an additional agent to the default Akonadi setup, add a file with the agent setup description into $KDEDIR/share/akonadi/firstrun. Such a file looks as follows: @verbatim [Agent] Id=defaultaddressbook Type=akonadi_vcard_resource Name=My Addressbook [Settings] Path[$e]=~/.kde/share/apps/kabc/std.ics AutosaveInterval=1 @endverbatim The keys in the [Agent] group are mandatory:
  • Id: A unique identifier of the setup description, should never change to avoid the agent being set up twice.
  • Type: The agent type
  • Nanme: The user visible name for this agent (only used for resource agents currently)
The [Settings] group is optional and contains agent-dependent settings. For those settings to be applied, the agent needs to export its settings via D-Bus using the KConfigXT <-> D-Bus bridge. */ class Firstrun : public QObject { Q_OBJECT public: Firstrun( QObject * parent = 0 ); ~Firstrun(); private: void findPendingDefaults(); void setupNext(); void migrateKresType( const QString &resourceFamily ); static QVariant::Type argumentType( const QMetaObject *mo, const QString &method ); private slots: void instanceCreated( KJob* job ); void migrationFinished( int exitCode ); private: QStringList mPendingDefaults; KConfig *mConfig; KConfig *mCurrentDefault; KProcess *mProcess; QString mResourceFamily; }; } #endif diff --git a/akonadi/resourcebase.cpp b/akonadi/resourcebase.cpp index c04c6d06b..f89537c2d 100644 --- a/akonadi/resourcebase.cpp +++ b/akonadi/resourcebase.cpp @@ -1,640 +1,642 @@ /* Copyright (c) 2006 Till Adam 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 "resourcebase.h" #include "agentbase_p.h" #include "resourceadaptor.h" #include "collectiondeletejob.h" #include "collectionsync_p.h" #include "itemsync.h" #include "resourcescheduler_p.h" #include "tracerinterface.h" #include "xdgbasedirs_p.h" #include "changerecorder.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "collectionmodifyjob.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "session.h" #include "resourceselectjob_p.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; class Akonadi::ResourceBasePrivate : public AgentBasePrivate { public: ResourceBasePrivate( ResourceBase *parent ) : AgentBasePrivate( parent ), scheduler( 0 ), mItemSyncer( 0 ), mCollectionSyncer( 0 ) { mStatusMessage = defaultReadyMessage(); } Q_DECLARE_PUBLIC( ResourceBase ) void delayedInit() { if ( !QDBusConnection::sessionBus().registerService( QLatin1String( "org.freedesktop.Akonadi.Resource." ) + mId ) ) kFatal() << "Unable to register service at D-Bus: " << QDBusConnection::sessionBus().lastError().message(); AgentBasePrivate::delayedInit(); } virtual void changeProcessed() { mMonitor->changeProcessed(); if ( !mMonitor->isEmpty() ) scheduler->scheduleChangeReplay(); scheduler->taskDone(); } void slotDeliveryDone( KJob* job ); void slotCollectionSyncDone( KJob *job ); void slotLocalListDone( KJob *job ); void slotSynchronizeCollection( const Collection &col ); void slotCollectionListDone( KJob *job ); void slotItemSyncDone( KJob *job ); void slotPercent( KJob* job, unsigned long percent ); void slotDeleteResourceCollection(); void slotDeleteResourceCollectionDone( KJob *job ); void slotCollectionDeletionDone( KJob *job ); void slotPrepareItemRetrieval( const Akonadi::Item &item ); void slotPrepareItemRetrievalResult( KJob* job ); // synchronize states Collection currentCollection; ResourceScheduler *scheduler; ItemSync *mItemSyncer; CollectionSync *mCollectionSyncer; }; ResourceBase::ResourceBase( const QString & id ) : AgentBase( new ResourceBasePrivate( this ), id ) { Q_D( ResourceBase ); new ResourceAdaptor( this ); d->scheduler = new ResourceScheduler( this ); d->mMonitor->setChangeRecordingEnabled( true ); connect( d->mMonitor, SIGNAL( changesAdded() ), d->scheduler, SLOT( scheduleChangeReplay() ) ); d->mMonitor->setResourceMonitored( d->mId.toLatin1() ); connect( d->scheduler, SIGNAL( executeFullSync() ), SLOT( retrieveCollections() ) ); connect( d->scheduler, SIGNAL( executeCollectionTreeSync() ), SLOT( retrieveCollections() ) ); connect( d->scheduler, SIGNAL( executeCollectionSync( const Akonadi::Collection& ) ), SLOT( slotSynchronizeCollection( const Akonadi::Collection& ) ) ); connect( d->scheduler, SIGNAL( executeItemFetch( const Akonadi::Item&, const QSet& ) ), SLOT( slotPrepareItemRetrieval(Akonadi::Item)) ); connect( d->scheduler, SIGNAL( executeResourceCollectionDeletion() ), SLOT( slotDeleteResourceCollection() ) ); connect( d->scheduler, SIGNAL( status( int, const QString& ) ), SIGNAL( status( int, const QString& ) ) ); connect( d->scheduler, SIGNAL( executeChangeReplay() ), d->mMonitor, SLOT( replayNext() ) ); connect( d->scheduler, SIGNAL( fullSyncComplete() ), SIGNAL( synchronized() ) ); connect( d->mMonitor, SIGNAL( nothingToReplay() ), d->scheduler, SLOT( taskDone() ) ); connect( this, SIGNAL( synchronized() ), d->scheduler, SLOT( taskDone() ) ); connect( this, SIGNAL( agentNameChanged( const QString& ) ), this, SIGNAL( nameChanged( const QString& ) ) ); d->scheduler->setOnline( d->mOnline ); if ( !d->mMonitor->isEmpty() ) d->scheduler->scheduleChangeReplay(); new ResourceSelectJob( identifier() ); } ResourceBase::~ResourceBase() { } void ResourceBase::synchronize() { d_func()->scheduler->scheduleFullSync(); } void ResourceBase::setName( const QString &name ) { AgentBase::setAgentName( name ); } QString ResourceBase::name() const { return AgentBase::agentName(); } QString ResourceBase::parseArguments( int argc, char **argv ) { QString identifier; if ( argc < 3 ) { kDebug() << "Not enough arguments passed..."; exit( 1 ); } for ( int i = 1; i < argc - 1; ++i ) { if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) identifier = QLatin1String( argv[ i + 1 ] ); } if ( identifier.isEmpty() ) { kDebug() << "Identifier argument missing"; exit( 1 ); } QByteArray catalog; char *p = strrchr( argv[0], '/' ); if ( p ) catalog = QByteArray( p + 1 ); else catalog = QByteArray( argv[0] ); KCmdLineArgs::init( argc, argv, identifier.toLatin1(), catalog, ki18nc("@title, application name", "Akonadi Resource"), "0.1", ki18nc("@title, application description", "Akonadi Resource") ); KCmdLineOptions options; options.add( "identifier ", ki18nc("@label, commandline option", "Resource identifier") ); KCmdLineArgs::addCmdLineOptions( options ); return identifier; } int ResourceBase::init( ResourceBase *r ) { QApplication::setQuitOnLastWindowClosed( false ); int rv = kapp->exec(); delete r; return rv; } void ResourceBase::itemRetrieved( const Item &item ) { Q_D( ResourceBase ); Q_ASSERT( d->scheduler->currentTask().type == ResourceScheduler::FetchItem ); if ( !item.isValid() ) { QDBusMessage reply( d->scheduler->currentTask().dbusMsg ); reply << false; QDBusConnection::sessionBus().send( reply ); d->scheduler->taskDone(); return; } Item i( item ); QSet requestedParts = d->scheduler->currentTask().itemParts; foreach ( const QByteArray &part, requestedParts ) { if ( !item.loadedPayloadParts().contains( part ) ) { kWarning() << "Item does not provide part" << part; } } ItemModifyJob *job = new ItemModifyJob( i ); // FIXME: remove once the item with which we call retrieveItem() has a revision number job->disableRevisionCheck(); connect( job, SIGNAL( result( KJob* ) ), SLOT( slotDeliveryDone( KJob* ) ) ); } void ResourceBasePrivate::slotDeliveryDone(KJob * job) { Q_Q( ResourceBase ); Q_ASSERT( scheduler->currentTask().type == ResourceScheduler::FetchItem ); QDBusMessage reply( scheduler->currentTask().dbusMsg ); if ( job->error() ) { emit q->error( QLatin1String( "Error while creating item: " ) + job->errorString() ); reply << false; } else { reply << true; } QDBusConnection::sessionBus().send( reply ); scheduler->taskDone(); } void ResourceBasePrivate::slotDeleteResourceCollection() { Q_Q( ResourceBase ); CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); job->fetchScope().setResource( q->identifier() ); connect( job, SIGNAL( result( KJob* ) ), q, SLOT( slotDeleteResourceCollectionDone( KJob* ) ) ); } void ResourceBasePrivate::slotDeleteResourceCollectionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); scheduler->taskDone(); } else { const CollectionFetchJob *fetchJob = static_cast( job ); if ( !fetchJob->collections().isEmpty() ) { CollectionDeleteJob *job = new CollectionDeleteJob( fetchJob->collections().first() ); connect( job, SIGNAL( result( KJob* ) ), q, SLOT( slotCollectionDeletionDone( KJob* ) ) ); } else { // there is no resource collection, so just ignore the request scheduler->taskDone(); } } } void ResourceBasePrivate::slotCollectionDeletionDone( KJob *job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBase::changeCommitted( const Item& item ) { Q_D( ResourceBase ); ItemModifyJob *job = new ItemModifyJob( item ); job->d_func()->setClean(); job->disableRevisionCheck(); // TODO: remove, but where/how do we handle the error? job->ignorePayload(); // we only want to reset the dirty flag and update the remote id d->changeProcessed(); } void ResourceBase::changeCommitted( const Collection &collection ) { Q_D( ResourceBase ); CollectionModifyJob *job = new CollectionModifyJob( collection ); Q_UNUSED( job ); //TODO: error checking d->changeProcessed(); } bool ResourceBase::requestItemDelivery( qint64 uid, const QString & remoteId, const QString &mimeType, const QStringList &_parts ) { Q_D( ResourceBase ); if ( !isOnline() ) { emit error( i18nc( "@info", "Cannot fetch item in offline mode." ) ); return false; } setDelayedReply( true ); // FIXME: we need at least the revision number too Item item( uid ); item.setMimeType( mimeType ); item.setRemoteId( remoteId ); QSet parts; Q_FOREACH( const QString &str, _parts ) parts.insert( str.toLatin1() ); d->scheduler->scheduleItemFetch( item, parts, message().createReply() ); return true; } void ResourceBase::collectionsRetrieved( const Collection::List & collections ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrieved()", "Calling collectionsRetrieved() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mCollectionSyncer, SIGNAL( result( KJob* ) ), SLOT( slotCollectionSyncDone( KJob* ) ) ); } d->mCollectionSyncer->setRemoteCollections( collections ); } void ResourceBase::collectionsRetrievedIncremental( const Collection::List & changedCollections, const Collection::List & removedCollections ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievedIncremental()", "Calling collectionsRetrievedIncremental() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mCollectionSyncer, SIGNAL( result( KJob* ) ), SLOT( slotCollectionSyncDone( KJob* ) ) ); } d->mCollectionSyncer->setRemoteCollections( changedCollections, removedCollections ); } void ResourceBase::setCollectionStreamingEnabled( bool enable ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::setCollectionStreamingEnabled()", "Calling setCollectionStreamingEnabled() although no collection retrieval is in progress" ); if ( !d->mCollectionSyncer ) { d->mCollectionSyncer = new CollectionSync( identifier() ); connect( d->mCollectionSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mCollectionSyncer, SIGNAL( result( KJob* ) ), SLOT( slotCollectionSyncDone( KJob* ) ) ); } d->mCollectionSyncer->setStreamingEnabled( enable ); } void ResourceBase::collectionsRetrievalDone() { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievalDone()", "Calling collectionsRetrievalDone() although no collection retrieval is in progress" ); // streaming enabled, so finalize the sync if ( d->mCollectionSyncer ) { d->mCollectionSyncer->retrievalDone(); } // user did the sync himself, we are done now else { + // FIXME: we need the same special case for SyncAll as in slotCollectionSyncDone here! d->scheduler->taskDone(); } } void ResourceBasePrivate::slotCollectionSyncDone( KJob * job ) { Q_Q( ResourceBase ); mCollectionSyncer = 0; if ( job->error() ) { emit q->error( job->errorString() ); } else { if ( scheduler->currentTask().type == ResourceScheduler::SyncAll ) { CollectionFetchJob *list = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); list->fetchScope().setResource( mId ); + list->fetchScope().setAncestorRetrieval( q->changeRecorder()->collectionFetchScope().ancestorRetrieval() ); q->connect( list, SIGNAL( result( KJob* ) ), q, SLOT( slotLocalListDone( KJob* ) ) ); return; } } scheduler->taskDone(); } void ResourceBasePrivate::slotLocalListDone( KJob * job ) { Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } else { Collection::List cols = static_cast( job )->collections(); foreach ( const Collection &col, cols ) { scheduler->scheduleSync( col ); } scheduler->scheduleFullSyncCompletion(); } scheduler->taskDone(); } void ResourceBasePrivate::slotSynchronizeCollection( const Collection &col ) { Q_Q( ResourceBase ); currentCollection = col; // check if this collection actually can contain anything QStringList contentTypes = currentCollection.contentMimeTypes(); contentTypes.removeAll( Collection::mimeType() ); if ( !contentTypes.isEmpty() ) { emit q->status( AgentBase::Running, i18nc( "@info:status", "Syncing collection '%1'", currentCollection.name() ) ); q->retrieveItems( currentCollection ); return; } scheduler->taskDone(); } void ResourceBasePrivate::slotPrepareItemRetrieval( const Akonadi::Item &item ) { Q_Q( ResourceBase ); ItemFetchJob *fetch = new ItemFetchJob( item, this ); fetch->fetchScope().setAncestorRetrieval( q->changeRecorder()->itemFetchScope().ancestorRetrieval() ); q->connect( fetch, SIGNAL(result(KJob*)), SLOT(slotPrepareItemRetrievalResult(KJob*)) ); } void ResourceBasePrivate::slotPrepareItemRetrievalResult( KJob* job ) { Q_Q( ResourceBase ); Q_ASSERT_X( scheduler->currentTask().type == ResourceScheduler::FetchItem, "ResourceBasePrivate::slotPrepareItemRetrievalResult()", "Preparing item retrieval although no item retrieval is in progress" ); if ( job->error() ) { q->cancelTask( job->errorText() ); return; } ItemFetchJob *fetch = qobject_cast( job ); if ( fetch->items().count() != 1 ) { q->cancelTask( QLatin1String("The requested item does no longer exist") ); return; } const Item item = fetch->items().first(); const QSet parts = scheduler->currentTask().itemParts; if ( !q->retrieveItem( item, parts ) ) q->cancelTask(); } void ResourceBase::itemsRetrievalDone() { Q_D( ResourceBase ); // streaming enabled, so finalize the sync if ( d->mItemSyncer ) { d->mItemSyncer->deliveryDone(); } // user did the sync himself, we are done now else { d->scheduler->taskDone(); } } void ResourceBase::clearCache() { Q_D( ResourceBase ); d->scheduler->scheduleResourceCollectionDeletion(); } Collection ResourceBase::currentCollection() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection , "ResourceBase::currentCollection()", "Trying to access current collection although no item retrieval is in progress" ); return d->currentCollection; } Item ResourceBase::currentItem() const { Q_D( const ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::FetchItem , "ResourceBase::currentItem()", "Trying to access current item although no item retrieval is in progress" ); return d->scheduler->currentTask().item; } void ResourceBase::synchronizeCollectionTree() { d_func()->scheduler->scheduleCollectionTreeSync(); } void ResourceBase::cancelTask() { Q_D( ResourceBase ); switch ( d->scheduler->currentTask().type ) { case ResourceScheduler::FetchItem: itemRetrieved( Item() ); // sends the error reply and break; case ResourceScheduler::ChangeReplay: d->changeProcessed(); break; default: d->scheduler->taskDone(); } } void ResourceBase::cancelTask( const QString &msg ) { cancelTask(); emit error( msg ); } void ResourceBase::deferTask() { Q_D( ResourceBase ); d->scheduler->deferTask(); } void ResourceBase::doSetOnline( bool state ) { d_func()->scheduler->setOnline( state ); } void ResourceBase::synchronizeCollection( qint64 collectionId ) { CollectionFetchJob* job = new CollectionFetchJob( Collection( collectionId ), CollectionFetchJob::Base ); job->fetchScope().setResource( identifier() ); job->fetchScope().setAncestorRetrieval( changeRecorder()->collectionFetchScope().ancestorRetrieval() ); connect( job, SIGNAL( result( KJob* ) ), SLOT( slotCollectionListDone( KJob* ) ) ); } void ResourceBasePrivate::slotCollectionListDone( KJob *job ) { if ( !job->error() ) { Collection::List list = static_cast( job )->collections(); if ( !list.isEmpty() ) { Collection col = list.first(); scheduler->scheduleSync( col ); } } // TODO: error handling } void ResourceBase::setTotalItems( int amount ) { kDebug() << amount; Q_D( ResourceBase ); setItemStreamingEnabled( true ); d->mItemSyncer->setTotalItems( amount ); } void ResourceBase::setItemStreamingEnabled( bool enable ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::setItemStreamingEnabled()", "Calling setItemStreamingEnabled() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mItemSyncer, SIGNAL( result( KJob* ) ), SLOT( slotItemSyncDone( KJob* ) ) ); } d->mItemSyncer->setStreamingEnabled( enable ); } void ResourceBase::itemsRetrieved( const Item::List &items ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrieved()", "Calling itemsRetrieved() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mItemSyncer, SIGNAL( result( KJob* ) ), SLOT( slotItemSyncDone( KJob* ) ) ); } d->mItemSyncer->setFullSyncItems( items ); } void ResourceBase::itemsRetrievedIncremental( const Item::List &changedItems, const Item::List &removedItems ) { Q_D( ResourceBase ); Q_ASSERT_X( d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::itemsRetrievedIncremental()", "Calling itemsRetrievedIncremental() although no item retrieval is in progress" ); if ( !d->mItemSyncer ) { d->mItemSyncer = new ItemSync( currentCollection() ); connect( d->mItemSyncer, SIGNAL( percent( KJob*, unsigned long ) ), SLOT( slotPercent( KJob*, unsigned long ) ) ); connect( d->mItemSyncer, SIGNAL( result( KJob* ) ), SLOT( slotItemSyncDone( KJob* ) ) ); } d->mItemSyncer->setIncrementalSyncItems( changedItems, removedItems ); } void ResourceBasePrivate::slotItemSyncDone( KJob *job ) { mItemSyncer = 0; Q_Q( ResourceBase ); if ( job->error() ) { emit q->error( job->errorString() ); } scheduler->taskDone(); } void ResourceBasePrivate::slotPercent( KJob *job, unsigned long percent ) { Q_Q( ResourceBase ); Q_UNUSED( job ); emit q->percent( percent ); } #include "resourcebase.moc" diff --git a/akonadi/resourcesynchronizationjob.h b/akonadi/resourcesynchronizationjob.h index cf1d23d58..1d50e0552 100644 --- a/akonadi/resourcesynchronizationjob.h +++ b/akonadi/resourcesynchronizationjob.h @@ -1,90 +1,90 @@ /* * Copyright (c) 2009 Volker Krause * * 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 . */ #ifndef AKONADI_RESOURCESYNCHRONIZATIONJOB_H #define AKONADI_RESOURCESYNCHRONIZATIONJOB_H #include "akonadi_export.h" #include namespace Akonadi { class AgentInstance; class ResourceSynchronizationJobPrivate; /** * @short Job that synchronizes a resource. * * This job will trigger a resource to synchronize the backend it is * responsible for (e.g. a local file or a groupware server) with the * Akonadi storage. * * If you only want to trigger the synchronization without being * interested in the result, using Akonadi::AgentInstance::synchronize() is enough. * If you want to wait until it's finished, use this class. * * Example: * * @code * using namespace Akonadi; * * const AgentInstance resource = AgentManager::self()->instance( "myresourceidentifier" ); * * ResourceSynchronizationJob *job = new ResourceSynchronizationJob( resource ); * connect( job, SIGNAL( result( KJob* ) ), SLOT( synchronizationFinished( KJob* ) ) ); * * @endcode * * @note This is a KJob not an Akonadi::Job, so it wont auto-start! * * @author Volker Krause * @since 4.4 */ class AKONADI_EXPORT ResourceSynchronizationJob : public KJob { Q_OBJECT public: /** * Creates a new synchronization job for the given resource. * * @param instance The resource instance to synchronize. */ - ResourceSynchronizationJob( const AgentInstance &instance, QObject *parent = 0 ); + explicit ResourceSynchronizationJob( const AgentInstance &instance, QObject *parent = 0 ); /** * Destroys the synchronization job. */ ~ResourceSynchronizationJob(); /* reimpl */ void start(); private: //@cond PRIVATE ResourceSynchronizationJobPrivate* const d; friend class ResourceSynchronizationJobPrivate; Q_PRIVATE_SLOT( d, void slotSynchronized() ) Q_PRIVATE_SLOT( d, void slotTimeout() ) //@endcond }; } #endif diff --git a/akonadi/tests/collectionjobtest.cpp b/akonadi/tests/collectionjobtest.cpp index 2ccac4526..4ec0d05d9 100644 --- a/akonadi/tests/collectionjobtest.cpp +++ b/akonadi/tests/collectionjobtest.cpp @@ -1,626 +1,643 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "collectionjobtest.h" #include #include "test_utils.h" #include "testattribute.h" #include "agentmanager.h" #include "agentinstance.h" #include "attributefactory.h" #include "cachepolicy.h" #include "collection.h" #include "collectioncreatejob.h" #include "collectiondeletejob.h" #include "collectionfetchjob.h" #include "collectionmodifyjob.h" #include "collectionselectjob_p.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "collectionpathresolver_p.h" #include "collectionutils_p.h" #include "control.h" #include "item.h" #include "kmime/messageparts.h" #include "resourceselectjob_p.h" #include "collectionfetchscope.h" #include #include using namespace Akonadi; QTEST_AKONADIMAIN( CollectionJobTest, NoGUI ) void CollectionJobTest::initTestCase() { qRegisterMetaType(); AttributeFactory::registerAttribute(); Control::start(); // switch all resources offline to reduce interference from them foreach ( Akonadi::AgentInstance agent, Akonadi::AgentManager::self()->instances() ) agent.setIsOnline( false ); } static Collection findCol( const Collection::List &list, const QString &name ) { foreach ( const Collection &col, list ) if ( col.name() == name ) return col; return Collection(); } // list compare which ignores the order template static void compareLists( const QList &l1, const QList &l2 ) { QCOMPARE( l1.count(), l2.count() ); foreach ( const T entry, l1 ) { QVERIFY( l2.contains( entry ) ); } } template static T* extractAttribute( QList attrs ) { T dummy; foreach ( Attribute* attr, attrs ) { if ( attr->type() == dummy.type() ) return dynamic_cast( attr ); } return 0; } static Collection::Id res1ColId = 6; // -1; static Collection::Id res2ColId = 7; //-1; static Collection::Id res3ColId = -1; static Collection::Id searchColId = -1; void CollectionJobTest::testTopLevelList( ) { // non-recursive top-level list CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); // check if everything is there and has the correct types and attributes QCOMPARE( list.count(), 4 ); Collection col; col = findCol( list, "res1" ); QVERIFY( col.isValid() ); res1ColId = col.id(); // for the next test QVERIFY( res1ColId > 0 ); QVERIFY( CollectionUtils::isResource( col ) ); QCOMPARE( col.parentCollection(), Collection::root() ); QCOMPARE( col.resource(), QLatin1String("akonadi_knut_resource_0") ); QVERIFY( findCol( list, "res2" ).isValid() ); res2ColId = findCol( list, "res2" ).id(); QVERIFY( res2ColId > 0 ); QVERIFY( findCol( list, "res3" ).isValid() ); res3ColId = findCol( list, "res3" ).id(); QVERIFY( res3ColId > 0 ); col = findCol( list, "Search" ); searchColId = col.id(); QVERIFY( col.isValid() ); QVERIFY( CollectionUtils::isVirtualParent( col ) ); QCOMPARE( col.resource(), QLatin1String("akonadi_search_resource") ); } void CollectionJobTest::testFolderList( ) { // recursive list of physical folders CollectionFetchJob *job = new CollectionFetchJob( Collection( res1ColId ), CollectionFetchJob::Recursive ); QSignalSpy spy( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)) ); QVERIFY( spy.isValid() ); QVERIFY( job->exec() ); Collection::List list = job->collections(); int count = 0; for ( int i = 0; i < spy.count(); ++i ) { Collection::List l = spy[i][0].value(); for ( int j = 0; j < l.count(); ++j ) { QVERIFY( list.count() > count + j ); QCOMPARE( list[count + j].id(), l[j].id() ); } count += l.count(); } QCOMPARE( count, list.count() ); // check if everything is there QCOMPARE( list.count(), 4 ); Collection col; QStringList contentTypes; col = findCol( list, "foo" ); QVERIFY( col.isValid() ); QCOMPARE( col.parentCollection().id(), res1ColId ); QVERIFY( CollectionUtils::isFolder( col ) ); contentTypes << "message/rfc822" << "text/calendar" << "text/directory" << "application/octet-stream" << "inode/directory"; compareLists( col.contentMimeTypes(), contentTypes ); QVERIFY( findCol( list, "bar" ).isValid() ); QCOMPARE( findCol( list, "bar" ).parentCollection(), col ); QVERIFY( findCol( list, "bla" ).isValid() ); } void CollectionJobTest::testNonRecursiveFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( res1ColId ), CollectionFetchJob::Base ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 1 ); QVERIFY( findCol( list, "res1" ).isValid() ); } void CollectionJobTest::testEmptyFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 0 ); } void CollectionJobTest::testSearchFolderList( ) { CollectionFetchJob *job = new CollectionFetchJob( Collection( searchColId ), CollectionFetchJob::FirstLevel ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 0 ); } void CollectionJobTest::testResourceFolderList() { // non-existing resource CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel ); job->fetchScope().setResource( "i_dont_exist" ); QVERIFY( !job->exec() ); // recursive listing of all collections of an existing resource job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); job->fetchScope().setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); Collection::List list = job->collections(); QCOMPARE( list.count(), 5 ); QVERIFY( findCol( list, "res1" ).isValid() ); QVERIFY( findCol( list, "foo" ).isValid() ); QVERIFY( findCol( list, "bar" ).isValid() ); QVERIFY( findCol( list, "bla" ).isValid() ); int fooId = findCol( list, "foo" ).id(); // limited listing of a resource job = new CollectionFetchJob( Collection( fooId ), CollectionFetchJob::Recursive ); job->fetchScope().setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); list = job->collections(); QCOMPARE( list.count(), 3 ); QVERIFY( findCol( list, "bar" ).isValid() ); QVERIFY( findCol( list, "bla" ).isValid() ); } void CollectionJobTest::testMimeTypeFilter() { CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); job->fetchScope().setContentMimeTypes( QStringList() << "message/rfc822" ); AKVERIFYEXEC( job ); Collection::List list = job->collections(); QCOMPARE( list.count(), 2 ); QVERIFY( findCol( list, "res1" ).isValid() ); QVERIFY( findCol( list, "foo" ).isValid() ); int fooId = findCol( list, "foo" ).id(); // limited listing of a resource job = new CollectionFetchJob( Collection( fooId ), CollectionFetchJob::Recursive ); job->fetchScope().setContentMimeTypes( QStringList() << "message/rfc822" ); AKVERIFYEXEC( job ); list = job->collections(); QCOMPARE( list.count(), 0 ); } void CollectionJobTest::testCreateDeleteFolder_data() { QTest::addColumn("collection"); QTest::addColumn("creatable"); Collection col; QTest::newRow("empty") << col << false; col.setName( "new folder" ); col.parentCollection().setId( res3ColId ); QTest::newRow("simple") << col << true; col.parentCollection().setId( res3ColId ); col.setName( "foo" ); QTest::newRow( "existing in different resource" ) << col << true; col.setName( "mail folder" ); QStringList mimeTypes; mimeTypes << "inode/directory" << "message/rfc822"; col.setContentMimeTypes( mimeTypes ); col.setRemoteId( "remote id" ); CachePolicy policy; policy.setInheritFromParent( false ); policy.setIntervalCheckTime( 60 ); policy.setLocalParts( QStringList( MessagePart::Envelope ) ); policy.setSyncOnDemand( true ); policy.setCacheTimeout( 120 ); col.setCachePolicy( policy ); QTest::newRow( "complex" ) << col << true; col = Collection(); col.setName( "New Folder" ); col.parentCollection().setId( searchColId ); QTest::newRow( "search folder" ) << col << false; col.parentCollection().setId( res2ColId ); col.setName( "foo2" ); QTest::newRow( "already existing" ) << col << false; col.setName( "Bla" ); col.parentCollection().setId( 2 ); QTest::newRow( "already existing with different case" ) << col << true; CollectionPathResolver *resolver = new CollectionPathResolver( "res2/foo2", this ); QVERIFY( resolver->exec() ); col.parentCollection().setId( resolver->collection() ); col.setName( "new folder" ); QTest::newRow( "parent noinferior" ) << col << false; col.parentCollection().setId( INT_MAX ); QTest::newRow( "missing parent" ) << col << false; col = Collection(); col.setName( "rid parent" ); col.parentCollection().setRemoteId( "8" ); QTest::newRow( "rid parent" ) << col << false; // missing resource context } void CollectionJobTest::testCreateDeleteFolder() { QFETCH( Collection, collection ); QFETCH( bool, creatable ); CollectionCreateJob *createJob = new CollectionCreateJob( collection, this ); QCOMPARE( createJob->exec(), creatable ); if ( !creatable ) return; Collection createdCol = createJob->collection(); QVERIFY( createdCol.isValid() ); QCOMPARE( createdCol.name(), collection.name() ); QCOMPARE( createdCol.parentCollection(), collection.parentCollection() ); QCOMPARE( createdCol.remoteId(), collection.remoteId() ); QCOMPARE( createdCol.cachePolicy(), collection.cachePolicy() ); CollectionFetchJob *listJob = new CollectionFetchJob( collection.parentCollection(), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); Collection listedCol = findCol( listJob->collections(), collection.name() ); QCOMPARE( listedCol, createdCol ); QCOMPARE( listedCol.remoteId(), collection.remoteId() ); QCOMPARE( listedCol.cachePolicy(), collection.cachePolicy() ); // fetch parent to compare inherited collection properties Collection parentCol = Collection::root(); if ( collection.parentCollection().isValid() ) { CollectionFetchJob *listJob = new CollectionFetchJob( collection.parentCollection(), CollectionFetchJob::Base, this ); AKVERIFYEXEC( listJob ); QCOMPARE( listJob->collections().count(), 1 ); parentCol = listJob->collections().first(); } if ( collection.contentMimeTypes().isEmpty() ) compareLists( listedCol.contentMimeTypes(), parentCol.contentMimeTypes() ); else compareLists( listedCol.contentMimeTypes(), collection.contentMimeTypes() ); if ( collection.resource().isEmpty() ) QCOMPARE( listedCol.resource(), parentCol.resource() ); else QCOMPARE( listedCol.resource(), collection.resource() ); CollectionDeleteJob *delJob = new CollectionDeleteJob( createdCol, this ); AKVERIFYEXEC( delJob ); listJob = new CollectionFetchJob( collection.parentCollection(), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); QVERIFY( !findCol( listJob->collections(), collection.name() ).isValid() ); } void CollectionJobTest::testIllegalDeleteFolder() { // non-existing folder CollectionDeleteJob *del = new CollectionDeleteJob( Collection( INT_MAX ), this ); QVERIFY( !del->exec() ); // root del = new CollectionDeleteJob( Collection::root(), this ); QVERIFY( !del->exec() ); } void CollectionJobTest::testStatistics() { // empty folder CollectionStatisticsJob *statistics = new CollectionStatisticsJob( Collection( res1ColId ), this ); QVERIFY( statistics->exec() ); CollectionStatistics s = statistics->statistics(); QCOMPARE( s.count(), 0ll ); QCOMPARE( s.unreadCount(), 0ll ); // folder with attributes and content CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this );; QVERIFY( resolver->exec() ); statistics = new CollectionStatisticsJob( Collection( resolver->collection() ), this ); QVERIFY( statistics->exec() ); s = statistics->statistics(); QCOMPARE( s.count(), 15ll ); QCOMPARE( s.unreadCount(), 14ll ); } void CollectionJobTest::testModify_data() { QTest::addColumn( "uid" ); QTest::addColumn( "rid" ); QTest::newRow( "uid" ) << collectionIdFromPath( "res1/foo" ) << QString(); QTest::newRow( "rid" ) << -1ll << QString( "10" ); } #define RESET_COLLECTION_ID \ col.setId( uid ); \ if ( !rid.isEmpty() ) col.setRemoteId( rid ) void CollectionJobTest::testModify() { QFETCH( qint64, uid ); QFETCH( QString, rid ); if ( !rid.isEmpty() ) { ResourceSelectJob *rjob = new ResourceSelectJob( "akonadi_knut_resource_0" ); AKVERIFYEXEC( rjob ); } QStringList reference; reference << "text/calendar" << "text/directory" << "message/rfc822" << "application/octet-stream" << "inode/directory"; Collection col; RESET_COLLECTION_ID; // test noop modify CollectionModifyJob *mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); CollectionFetchJob* ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); compareLists( col.contentMimeTypes(), reference ); // test clearing content types RESET_COLLECTION_ID; col.setContentMimeTypes( QStringList() ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); QVERIFY( col.contentMimeTypes().isEmpty() ); // test setting contnet types RESET_COLLECTION_ID; col.setContentMimeTypes( reference ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); compareLists( col.contentMimeTypes(), reference ); // add attribute RESET_COLLECTION_ID; col.attribute( Collection::AddIfMissing )->data = "new"; mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QVERIFY( ljob->collections().first().hasAttribute() ); QCOMPARE( ljob->collections().first().attribute()->data, QByteArray( "new" ) ); // modify existing attribute RESET_COLLECTION_ID; col.attribute()->data = "modified"; mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QVERIFY( ljob->collections().first().hasAttribute() ); QCOMPARE( ljob->collections().first().attribute()->data, QByteArray( "modified" ) ); // renaming RESET_COLLECTION_ID; col.setName( "foo (renamed)" ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); ljob = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); AKVERIFYEXEC( ljob ); QCOMPARE( ljob->collections().count(), 1 ); col = ljob->collections().first(); QCOMPARE( col.name(), QString( "foo (renamed)" ) ); RESET_COLLECTION_ID; col.setName( "foo" ); mod = new CollectionModifyJob( col, this ); AKVERIFYEXEC( mod ); } #undef RESET_COLLECTION_ID void CollectionJobTest::testIllegalModify() { // non-existing collection Collection col( INT_MAX ); col.parentCollection().setId( res1ColId ); CollectionModifyJob *mod = new CollectionModifyJob( col, this ); QVERIFY( !mod->exec() ); // rename to already existing name col = Collection( res1ColId ); col.setName( "res2" ); mod = new CollectionModifyJob( col, this ); QVERIFY( !mod->exec() ); } void CollectionJobTest::testUtf8CollectionName() { QString folderName = QString::fromUtf8( "รค" ); // create collection Collection col; col.parentCollection().setId( res3ColId ); col.setName( folderName ); CollectionCreateJob *create = new CollectionCreateJob( col, this ); QVERIFY( create->exec() ); col = create->collection(); QVERIFY( col.isValid() ); // list parent CollectionFetchJob *list = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::Recursive, this ); QVERIFY( list->exec() ); QCOMPARE( list->collections().count(), 1 ); QCOMPARE( col, list->collections().first() ); QCOMPARE( col.name(), folderName ); // modify collection col.setContentMimeTypes( QStringList( "message/rfc822'" ) ); CollectionModifyJob *modify = new CollectionModifyJob( col, this ); QVERIFY( modify->exec() ); // collection statistics CollectionStatisticsJob *statistics = new CollectionStatisticsJob( col, this ); QVERIFY( statistics->exec() ); CollectionStatistics s = statistics->statistics(); QCOMPARE( s.count(), 0ll ); QCOMPARE( s.unreadCount(), 0ll ); // delete collection CollectionDeleteJob *del = new CollectionDeleteJob( col, this ); QVERIFY( del->exec() ); } void CollectionJobTest::testMultiList() { Collection::List req; req << Collection( res1ColId ) << Collection( res2ColId ); CollectionFetchJob* job = new CollectionFetchJob( req, this ); QVERIFY( job->exec() ); Collection::List res; res = job->collections(); compareLists( res, req ); } void CollectionJobTest::testSelect() { CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this );; QVERIFY( resolver->exec() ); Collection col( resolver->collection() ); CollectionSelectJob *job = new CollectionSelectJob( col, this ); QVERIFY( job->exec() ); QCOMPARE( job->unseen(), -1 ); job = new CollectionSelectJob( col, this ); job->setRetrieveStatus( true ); QVERIFY( job->exec() ); QVERIFY( job->unseen() > -1 ); job = new CollectionSelectJob( Collection::root(), this ); QVERIFY( job->exec() ); job = new CollectionSelectJob( Collection( INT_MAX ), this ); QVERIFY( !job->exec() ); } void CollectionJobTest::testRidFetch() { Collection col; col.setRemoteId( "10" ); CollectionFetchJob *job = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); job->fetchScope().setResource( "akonadi_knut_resource_0" ); QVERIFY( job->exec() ); QCOMPARE( job->collections().count(), 1 ); col = job->collections().first(); QVERIFY( col.isValid() ); QCOMPARE( col.remoteId(), QString::fromLatin1( "10" ) ); } void CollectionJobTest::testRidCreateDelete() { Collection collection; collection.setName( "rid create" ); collection.parentCollection().setRemoteId( "8" ); collection.setRemoteId( "MY REMOTE ID" ); ResourceSelectJob *resSel = new ResourceSelectJob( "akonadi_knut_resource_2" ); AKVERIFYEXEC( resSel ); CollectionCreateJob *createJob = new CollectionCreateJob( collection, this ); AKVERIFYEXEC( createJob ); Collection createdCol = createJob->collection(); QVERIFY( createdCol.isValid() ); QCOMPARE( createdCol.name(), collection.name() ); CollectionFetchJob *listJob = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); Collection listedCol = findCol( listJob->collections(), collection.name() ); QCOMPARE( listedCol, createdCol ); QCOMPARE( listedCol.name(), collection.name() ); QVERIFY( !collection.isValid() ); CollectionDeleteJob *delJob = new CollectionDeleteJob( collection, this ); AKVERIFYEXEC( delJob ); listJob = new CollectionFetchJob( Collection( res3ColId ), CollectionFetchJob::FirstLevel, this ); AKVERIFYEXEC( listJob ); QVERIFY( !findCol( listJob->collections(), collection.name() ).isValid() ); } +void CollectionJobTest::testAncestorRetrieval() +{ + Collection col; + col.setRemoteId( "10" ); + + CollectionFetchJob *job = new CollectionFetchJob( col, CollectionFetchJob::Base, this ); + job->fetchScope().setResource( "akonadi_knut_resource_0" ); + job->fetchScope().setAncestorRetrieval( CollectionFetchScope::All ); + AKVERIFYEXEC( job ); + QCOMPARE( job->collections().count(), 1 ); + col = job->collections().first(); + QVERIFY( col.isValid() ); + QVERIFY( col.parentCollection().isValid() ); + QCOMPARE( col.parentCollection().remoteId(), QString( "6" ) ); + QCOMPARE( col.parentCollection().parentCollection(), Collection::root() ); +} + #include "collectionjobtest.moc" diff --git a/akonadi/tests/collectionjobtest.h b/akonadi/tests/collectionjobtest.h index e704621d2..52c67755d 100644 --- a/akonadi/tests/collectionjobtest.h +++ b/akonadi/tests/collectionjobtest.h @@ -1,52 +1,53 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef COLLECTIONJOBTEST_H #define COLLECTIONJOBTEST_H #include class CollectionJobTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testTopLevelList(); void testFolderList(); void testNonRecursiveFolderList(); void testEmptyFolderList(); void testSearchFolderList(); void testResourceFolderList(); void testMimeTypeFilter(); void testCreateDeleteFolder_data(); void testCreateDeleteFolder(); void testIllegalDeleteFolder(); void testStatistics(); void testModify_data(); void testModify(); void testIllegalModify(); void testUtf8CollectionName(); void testMultiList(); void testSelect(); void testRidFetch(); void testRidCreateDelete(); + void testAncestorRetrieval(); }; #endif diff --git a/akonadi/tests/entitycachetest.cpp b/akonadi/tests/entitycachetest.cpp index a862d0354..667f2aa40 100644 --- a/akonadi/tests/entitycachetest.cpp +++ b/akonadi/tests/entitycachetest.cpp @@ -1,131 +1,136 @@ /* Copyright (c) 2009 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 "entitycache.cpp" #include #include using namespace Akonadi; class EntityCacheTest : public QObject { Q_OBJECT private: template void testCache() { EntityCache cache( 2 ); QSignalSpy spy( &cache, SIGNAL(dataAvailable()) ); QVERIFY( spy.isValid() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( !cache.isRequested( 1 ) ); QVERIFY( !cache.retrieve( 1 ).isValid() ); - cache.request( 1, FetchScope() ); + FetchScope scope; + scope.setAncestorRetrieval( FetchScope::All ); + + cache.request( 1, scope ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); QVERIFY( !cache.retrieve( 1 ).isValid() ); QTest::qWait( 1000 ); QCOMPARE( spy.count(), 1 ); QVERIFY( cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); const T e1 = cache.retrieve( 1 ); QCOMPARE( e1.id(), 1ll ); + QVERIFY( e1.parentCollection().isValid() ); + QVERIFY( !e1.parentCollection().remoteId().isEmpty() || e1.parentCollection() == Collection::root() ); spy.clear(); cache.request( 2, FetchScope() ); cache.request( 3, FetchScope() ); QVERIFY( !cache.isCached( 1 ) ); QVERIFY( !cache.isRequested( 1 ) ); QVERIFY( cache.isRequested( 2 ) ); QVERIFY( cache.isRequested( 3 ) ); cache.invalidate( 2 ); QTest::qWait( 1000 ); QCOMPARE( spy.count(), 2 ); QVERIFY( cache.isCached( 2 ) ); QVERIFY( cache.isCached( 3 ) ); const T e2 = cache.retrieve( 2 ); const T e3a = cache.retrieve( 3 ); QCOMPARE( e3a.id(), 3ll ); QVERIFY( !e2.isValid() ); cache.invalidate( 3 ); const T e3b = cache.retrieve( 3 ); QVERIFY( !e3b.isValid() ); spy.clear(); cache.update( 3, FetchScope() ); cache.update( 3, FetchScope() ); QVERIFY( !cache.isCached( 3 ) ); QVERIFY( cache.isRequested( 3 ) ); QVERIFY( !cache.retrieve( 3 ).isValid() ); QTest::qWait( 1000 ); QCOMPARE( spy.count(), 1 ); QVERIFY( cache.isCached( 3 ) ); QVERIFY( cache.retrieve( 3 ).isValid() ); } private slots: void testCacheGeneric_data() { QTest::addColumn( "collection" ); QTest::newRow( "collection" ) << true; QTest::newRow( "item" ) << false; } void testCacheGeneric() { QFETCH( bool, collection ); if ( collection ) testCache(); else testCache(); } void testItemCache() { ItemCache cache( 1 ); QSignalSpy spy( &cache, SIGNAL(dataAvailable()) ); QVERIFY( spy.isValid() ); ItemFetchScope scope; scope.fetchFullPayload( true ); cache.request( 1, scope ); QTest::qWait( 1000 ); QCOMPARE( spy.count(), 1 ); QVERIFY( cache.isCached( 1 ) ); QVERIFY( cache.isRequested( 1 ) ); const Item item = cache.retrieve( 1 ); QCOMPARE( item.id(), 1ll ); QVERIFY( item.hasPayload() ); } }; QTEST_AKONADIMAIN( EntityCacheTest, NoGUI ) #include "entitycachetest.moc" diff --git a/akonadi/transportresourcebase.h b/akonadi/transportresourcebase.h index 2b08532e4..2c66d163a 100644 --- a/akonadi/transportresourcebase.h +++ b/akonadi/transportresourcebase.h @@ -1,81 +1,81 @@ /* Copyright (c) 2009 Constantin Berzan 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_TRANSPORTRESOURCEBASE_H #define AKONADI_TRANSPORTRESOURCEBASE_H #include "akonadi_export.h" -#include +#include #include class KJob; namespace Akonadi { class TransportResourceBasePrivate; /** * @short Resource implementing mail transport capability. * * To implement a transport-enabled resource, inherit from both * ResourceBase and TransportResourceBase, implement the virtual method * sendItem(), and call emitTransportResult() when finished sending. * The resource must also have the "MailTransport" capability flag. * * For an example of a transport-enabled resource, see * kdepim/akonadi/resources/mailtransport_dummy. * * @author Constantin Berzan * @since 4.4 */ class AKONADI_EXPORT TransportResourceBase { public: TransportResourceBase(); virtual ~TransportResourceBase(); /** Reimplement in your resource, to begin the actual sending operation. Call emitTransportResult() when finished. @param message The ID of the message to be sent. @see emitTransportResult. */ virtual void sendItem( Akonadi::Item::Id message ) = 0; // TODO add void emitSendProgress( int percent ); /** * Emits the transportResult() DBus signal with the given arguments. * @param item The id of the item that was sent. * @param success True if the sending operation succeeded. * @param message An optional textual explanation of the result. * @see Transport. */ void emitTransportResult( Akonadi::Item::Id &item, bool success, const QString &message = QString() ); private: TransportResourceBasePrivate *const d; }; } // namespace Akonadi #endif // AKONADI_TRANSPORTRESOURCEBASE_H diff --git a/includes/KCal/HtmlExportSettings b/includes/KCal/HtmlExportSettings deleted file mode 100644 index 2b1e5d09d..000000000 --- a/includes/KCal/HtmlExportSettings +++ /dev/null @@ -1 +0,0 @@ -#include "../../kcal/htmlexportsettings.h" diff --git a/kcal/icalformat_p.cpp b/kcal/icalformat_p.cpp index 0662cf4a9..2576bffd2 100644 --- a/kcal/icalformat_p.cpp +++ b/kcal/icalformat_p.cpp @@ -1,2675 +1,2675 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2006 David Jarvie 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 API for handling calendar data and defines the internal ICalFormat classes. @brief This class provides the libical dependent functions for ICalFormat. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ @author David Jarvie \ */ #include "icalformat_p.h" #include "icalformat.h" #include "icaltimezones.h" #include "calendar.h" #include "compat.h" #include "journal.h" extern "C" { #include #include #include } #include #include #include #include #include #include #include #include #include #include #include using namespace KCal; /* Static helpers */ /* static void _dumpIcaltime( const icaltimetype& t) { kDebug() << "--- Y:" << t.year << "M:" << t.month << "D:" << t.day; kDebug() << "--- H:" << t.hour << "M:" << t.minute << "S:" << t.second; kDebug() << "--- isUtc:" << icaltime_is_utc( t ); kDebug() << "--- zoneId:" << icaltimezone_get_tzid( const_cast( t.zone ) ); } */ //@cond PRIVATE static QString quoteForParam( const QString &text ) { QString tmp = text; tmp.remove( '"' ); if ( tmp.contains( ';' ) || tmp.contains( ':' ) || tmp.contains( ',' ) ) { return tmp; // libical quotes in this case already, see icalparameter_as_ical_string() } return QString::fromLatin1( "\"" ) + tmp + QString::fromLatin1( "\"" ); } const int gSecondsPerMinute = 60; const int gSecondsPerHour = gSecondsPerMinute * 60; const int gSecondsPerDay = gSecondsPerHour * 24; const int gSecondsPerWeek = gSecondsPerDay * 7; class ToComponentVisitor : public IncidenceBase::Visitor { public: ToComponentVisitor( ICalFormatImpl *impl, iTIPMethod m ) : mImpl( impl ), mComponent( 0 ), mMethod( m ) {} bool visit( Event *e ) { mComponent = mImpl->writeEvent( e ); return true; } bool visit( Todo *e ) { mComponent = mImpl->writeTodo( e ); return true; } bool visit( Journal *e ) { mComponent = mImpl->writeJournal( e ); return true; } bool visit( FreeBusy *fb ) { mComponent = mImpl->writeFreeBusy( fb, mMethod ); return true; } icalcomponent *component() { return mComponent; } private: ICalFormatImpl *mImpl; icalcomponent *mComponent; iTIPMethod mMethod; }; class ICalFormatImpl::Private { public: Private( ICalFormatImpl *impl, ICalFormat *parent ) : mImpl( impl ), mParent( parent ), mCompat( new Compat ) {} ~Private() { delete mCompat; } void writeIncidenceBase( icalcomponent *parent, IncidenceBase * ); void readIncidenceBase( icalcomponent *parent, IncidenceBase * ); void writeCustomProperties( icalcomponent *parent, CustomProperties * ); void readCustomProperties( icalcomponent *parent, CustomProperties * ); ICalFormatImpl *mImpl; ICalFormat *mParent; QString mLoadedProductId; // PRODID string loaded from calendar file Event::List mEventsRelate; // events with relations Todo::List mTodosRelate; // todos with relations Compat *mCompat; }; //@endcond inline icaltimetype ICalFormatImpl::writeICalUtcDateTime ( const KDateTime &dt ) { return writeICalDateTime( dt.toUtc() ); } ICalFormatImpl::ICalFormatImpl( ICalFormat *parent ) : d( new Private( this, parent ) ) { } ICalFormatImpl::~ICalFormatImpl() { delete d; } QString ICalFormatImpl::loadedProductId() const { return d->mLoadedProductId; } icalcomponent *ICalFormatImpl::writeIncidence( IncidenceBase *incidence, iTIPMethod method ) { ToComponentVisitor v( this, method ); if ( incidence->accept(v) ) { return v.component(); } else { return 0; } } icalcomponent *ICalFormatImpl::writeTodo( Todo *todo, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { QString tmpStr; QStringList tmpStrList; icalcomponent *vtodo = icalcomponent_new( ICAL_VTODO_COMPONENT ); writeIncidence( vtodo, todo, tzlist, tzUsedList ); // due date icalproperty *prop; if ( todo->hasDueDate() ) { icaltimetype due; if ( todo->allDay() ) { due = writeICalDate( todo->dtDue( true ).date() ); prop = icalproperty_new_due(due); } else { prop = writeICalDateTimeProperty( ICAL_DUE_PROPERTY, todo->dtDue(true), tzlist, tzUsedList ); } icalcomponent_add_property( vtodo, prop ); } // start time if ( todo->hasStartDate() ) { icaltimetype start; if ( todo->allDay() ) { start = writeICalDate( todo->dtStart( true ).date() ); prop = icalproperty_new_dtstart( start ); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, todo->dtStart( true ), tzlist, tzUsedList ); } icalcomponent_add_property( vtodo, prop ); } // completion date (UTC) if ( todo->isCompleted() ) { if ( !todo->hasCompletedDate() ) { // If the todo was created by KOrganizer<2.2 it does not have // a correct completion date. Set one now. todo->setCompleted( KDateTime::currentUtcDateTime() ); } icaltimetype completed = writeICalUtcDateTime( todo->completed() ); icalcomponent_add_property( vtodo, icalproperty_new_completed ( completed ) ); } icalcomponent_add_property( vtodo, icalproperty_new_percentcomplete( todo->percentComplete() ) ); if ( todo->recurs() ) { icalcomponent_add_property( vtodo, writeICalDateTimeProperty( ICAL_RECURRENCEID_PROPERTY, todo->dtDue(), tzlist, tzUsedList ) ); } return vtodo; } icalcomponent *ICalFormatImpl::writeEvent( Event *event, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { icalcomponent *vevent = icalcomponent_new( ICAL_VEVENT_COMPONENT ); writeIncidence( vevent, event, tzlist, tzUsedList ); // start time icalproperty *prop; icaltimetype start; if ( event->allDay() ) { start = writeICalDate( event->dtStart().date() ); prop = icalproperty_new_dtstart( start ); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, event->dtStart(), tzlist, tzUsedList ); } icalcomponent_add_property( vevent, prop ); if ( event->hasEndDate() ) { // End time. // RFC2445 says that if DTEND is present, it has to be greater than DTSTART. icaltimetype end; KDateTime dt = event->dtEnd(); if ( event->allDay() ) { // +1 day because end date is non-inclusive. end = writeICalDate( dt.date().addDays( 1 ) ); icalcomponent_add_property( vevent, icalproperty_new_dtend(end) ); } else { if ( dt != event->dtStart() ) { icalcomponent_add_property( vevent, writeICalDateTimeProperty( ICAL_DTEND_PROPERTY, dt, tzlist, tzUsedList ) ); } } } // TODO: resources #if 0 // resources QStringList tmpStrList = anEvent->resources(); QString tmpStr = tmpStrList.join( ";" ); if ( !tmpStr.isEmpty() ) { addPropValue( vevent, VCResourcesProp, tmpStr.toUtf8() ); } #endif // Transparency switch( event->transparency() ) { case Event::Transparent: icalcomponent_add_property( vevent, icalproperty_new_transp( ICAL_TRANSP_TRANSPARENT ) ); break; case Event::Opaque: icalcomponent_add_property( vevent, icalproperty_new_transp( ICAL_TRANSP_OPAQUE ) ); break; } return vevent; } icalcomponent *ICalFormatImpl::writeFreeBusy( FreeBusy *freebusy, iTIPMethod method ) { icalcomponent *vfreebusy = icalcomponent_new( ICAL_VFREEBUSY_COMPONENT ); d->writeIncidenceBase( vfreebusy, freebusy ); icalcomponent_add_property( vfreebusy, icalproperty_new_dtstart( writeICalUtcDateTime( freebusy->dtStart() ) ) ); icalcomponent_add_property( vfreebusy, icalproperty_new_dtend( writeICalUtcDateTime( freebusy->dtEnd() ) ) ); if ( method == iTIPRequest ) { icalcomponent_add_property( vfreebusy, icalproperty_new_uid( freebusy->uid().toUtf8() ) ); } //Loops through all the periods in the freebusy object QList list = freebusy->busyPeriods(); icalperiodtype period = icalperiodtype_null_period(); for ( int i = 0, count = list.count(); i < count; ++i ) { period.start = writeICalUtcDateTime( list[i].start() ); if ( list[i].hasDuration() ) { period.duration = writeICalDuration( list[i].duration() ); } else { period.end = writeICalUtcDateTime( list[i].end() ); } icalcomponent_add_property( vfreebusy, icalproperty_new_freebusy( period ) ); } return vfreebusy; } icalcomponent *ICalFormatImpl::writeJournal( Journal *journal, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { icalcomponent *vjournal = icalcomponent_new( ICAL_VJOURNAL_COMPONENT ); writeIncidence( vjournal, journal, tzlist, tzUsedList ); // start time icalproperty *prop; KDateTime dt = journal->dtStart(); if ( dt.isValid() ) { icaltimetype start; if ( journal->allDay() ) { start = writeICalDate( dt.date() ); prop = icalproperty_new_dtstart( start ); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, dt, tzlist, tzUsedList ); } icalcomponent_add_property( vjournal, prop ); } return vjournal; } void ICalFormatImpl::writeIncidence( icalcomponent *parent, Incidence *incidence, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { if ( incidence->schedulingID() != incidence->uid() ) { // We need to store the UID in here. The rawSchedulingID will // go into the iCal UID component incidence->setCustomProperty( "LIBKCAL", "ID", incidence->uid() ); } else { incidence->removeCustomProperty( "LIBKCAL", "ID" ); } d->writeIncidenceBase( parent, incidence ); // creation date icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_CREATED_PROPERTY, incidence->created() ) ); // unique id // If the scheduling ID is different from the real UID, the real // one is stored on X-REALID above if ( !incidence->schedulingID().isEmpty() ) { icalcomponent_add_property( parent, icalproperty_new_uid( incidence->schedulingID().toUtf8() ) ); } // revision if ( incidence->revision() > 0 ) { // 0 is default, so don't write that out icalcomponent_add_property( parent, icalproperty_new_sequence( incidence->revision() ) ); } // last modification date if ( incidence->lastModified().isValid() ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_LASTMODIFIED_PROPERTY, incidence->lastModified() ) ); } // description if ( !incidence->description().isEmpty() ) { icalcomponent_add_property( parent, writeDescription( incidence->description(), incidence->descriptionIsRich() ) ); } // summary if ( !incidence->summary().isEmpty() ) { icalcomponent_add_property( parent, writeSummary( incidence->summary(), incidence->summaryIsRich() ) ); } // location if ( !incidence->location().isEmpty() ) { icalcomponent_add_property( parent, writeLocation( incidence->location(), incidence->locationIsRich() ) ); } // status icalproperty_status status = ICAL_STATUS_NONE; switch ( incidence->status() ) { case Incidence::StatusTentative: status = ICAL_STATUS_TENTATIVE; break; case Incidence::StatusConfirmed: status = ICAL_STATUS_CONFIRMED; break; case Incidence::StatusCompleted: status = ICAL_STATUS_COMPLETED; break; case Incidence::StatusNeedsAction: status = ICAL_STATUS_NEEDSACTION; break; case Incidence::StatusCanceled: status = ICAL_STATUS_CANCELLED; break; case Incidence::StatusInProcess: status = ICAL_STATUS_INPROCESS; break; case Incidence::StatusDraft: status = ICAL_STATUS_DRAFT; break; case Incidence::StatusFinal: status = ICAL_STATUS_FINAL; break; case Incidence::StatusX: { icalproperty *p = icalproperty_new_status( ICAL_STATUS_X ); icalvalue_set_x( icalproperty_get_value( p ), incidence->statusStr().toUtf8() ); icalcomponent_add_property( parent, p ); break; } case Incidence::StatusNone: default: break; } if ( status != ICAL_STATUS_NONE ) { icalcomponent_add_property( parent, icalproperty_new_status( status ) ); } // secrecy icalproperty_class secClass; switch ( incidence->secrecy() ) { case Incidence::SecrecyPublic: secClass = ICAL_CLASS_PUBLIC; break; case Incidence::SecrecyConfidential: secClass = ICAL_CLASS_CONFIDENTIAL; break; case Incidence::SecrecyPrivate: default: secClass = ICAL_CLASS_PRIVATE; break; } if ( secClass != ICAL_CLASS_PUBLIC ) { icalcomponent_add_property( parent, icalproperty_new_class( secClass ) ); } // geo if ( incidence->hasGeo() ) { icalgeotype geo; geo.lat = incidence->geoLatitude(); geo.lon = incidence->geoLongitude(); icalcomponent_add_property( parent, icalproperty_new_geo( geo ) ); } // priority if ( incidence->priority() > 0 ) { // 0 is undefined priority icalcomponent_add_property( parent, icalproperty_new_priority( incidence->priority() ) ); } // categories QString categories = incidence->categories().join( "," ); if ( !categories.isEmpty() ) { icalcomponent_add_property( parent, icalproperty_new_categories( categories.toUtf8() ) ); } // related event if ( !incidence->relatedToUid().isEmpty() ) { icalcomponent_add_property( parent, icalproperty_new_relatedto( incidence->relatedToUid().toUtf8() ) ); } RecurrenceRule::List rrules( incidence->recurrence()->rRules() ); RecurrenceRule::List::ConstIterator rit; for ( rit = rrules.constBegin(); rit != rrules.constEnd(); ++rit ) { icalcomponent_add_property( parent, icalproperty_new_rrule( writeRecurrenceRule( (*rit) ) ) ); } RecurrenceRule::List exrules( incidence->recurrence()->exRules() ); RecurrenceRule::List::ConstIterator exit; for ( exit = exrules.constBegin(); exit != exrules.constEnd(); ++exit ) { icalcomponent_add_property( parent, icalproperty_new_exrule( writeRecurrenceRule( (*exit) ) ) ); } DateList dateList = incidence->recurrence()->exDates(); DateList::ConstIterator exIt; for ( exIt = dateList.constBegin(); exIt != dateList.constEnd(); ++exIt ) { icalcomponent_add_property( parent, icalproperty_new_exdate( writeICalDate(*exIt) ) ); } DateTimeList dateTimeList = incidence->recurrence()->exDateTimes(); DateTimeList::ConstIterator extIt; for ( extIt = dateTimeList.constBegin(); extIt != dateTimeList.constEnd(); ++extIt ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_EXDATE_PROPERTY, *extIt, tzlist, tzUsedList ) ); } dateList = incidence->recurrence()->rDates(); DateList::ConstIterator rdIt; for ( rdIt = dateList.constBegin(); rdIt != dateList.constEnd(); ++rdIt ) { icalcomponent_add_property( parent, icalproperty_new_rdate( writeICalDatePeriod(*rdIt) ) ); } dateTimeList = incidence->recurrence()->rDateTimes(); DateTimeList::ConstIterator rdtIt; for ( rdtIt = dateTimeList.constBegin(); rdtIt != dateTimeList.constEnd(); ++rdtIt ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_RDATE_PROPERTY, *rdtIt, tzlist, tzUsedList ) ); } // attachments Attachment::List attachments = incidence->attachments(); Attachment::List::ConstIterator atIt; for ( atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt ) { icalcomponent_add_property( parent, writeAttachment( *atIt ) ); } // alarms Alarm::List::ConstIterator alarmIt; for ( alarmIt = incidence->alarms().constBegin(); alarmIt != incidence->alarms().constEnd(); ++alarmIt ) { if ( (*alarmIt)->enabled() ) { icalcomponent_add_component( parent, writeAlarm( *alarmIt ) ); } } // duration if ( incidence->hasDuration() ) { icaldurationtype duration; duration = writeICalDuration( incidence->duration() ); icalcomponent_add_property( parent, icalproperty_new_duration( duration ) ); } } //@cond PRIVATE void ICalFormatImpl::Private::writeIncidenceBase( icalcomponent *parent, IncidenceBase *incidenceBase ) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_DTSTAMP_PROPERTY, KDateTime::currentUtcDateTime() ) ); // organizer stuff if ( !incidenceBase->organizer().isEmpty() ) { icalproperty *p = mImpl->writeOrganizer( incidenceBase->organizer() ); if ( p ) { icalcomponent_add_property( parent, p ); } } // attendees if ( incidenceBase->attendeeCount() > 0 ) { Attendee::List::ConstIterator it; for ( it = incidenceBase->attendees().constBegin(); it != incidenceBase->attendees().constEnd(); ++it ) { icalproperty *p = mImpl->writeAttendee( *it ); if ( p ) { icalcomponent_add_property( parent, p ); } } } // comments QStringList comments = incidenceBase->comments(); for ( QStringList::const_iterator it = comments.constBegin(); it != comments.constEnd(); ++it ) { icalcomponent_add_property( parent, icalproperty_new_comment( (*it).toUtf8() ) ); } // custom properties writeCustomProperties( parent, incidenceBase ); } void ICalFormatImpl::Private::writeCustomProperties( icalcomponent *parent, CustomProperties *properties ) { const QMap custom = properties->customProperties(); for ( QMap::ConstIterator c = custom.begin(); c != custom.end(); ++c ) { icalproperty *p = icalproperty_new_x( c.value().toUtf8() ); if ( !c.key().startsWith( "X-KDE" ) && //krazy:exclude=strings !c.key().startsWith( "X-LibKCal" ) && //krazy:exclude=strings !c.key().startsWith( "X-MICROSOFT" ) && //krazy:exclude=strings - !c.key().startsWith( "X-MOZILLA" ) && //krazy:exclude=strings + !c.key().startsWith( "X-MOZ" ) && //krazy:exclude=strings !c.key().startsWith( "X-PILOT" ) ) { //krazy:exclude=strings // use text values for the typical X-FOO property. // except for vendor specific X-FOO properties. icalvalue *text = icalvalue_new_text( c.value().toUtf8().data() ); icalproperty_set_value( p, text ); } icalproperty_set_x_name( p, c.key() ); icalcomponent_add_property( parent, p ); } } //@endcond icalproperty *ICalFormatImpl::writeOrganizer( const Person &organizer ) { if ( organizer.email().isEmpty() ) { return 0; } icalproperty *p = icalproperty_new_organizer( "MAILTO:" + organizer.email().toUtf8() ); if ( !organizer.name().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_cn( quoteForParam( organizer.name() ).toUtf8() ) ); } // TODO: Write dir, sent-by and language return p; } icalproperty *ICalFormatImpl::writeDescription( const QString &description, bool isRich ) { icalproperty *p = icalproperty_new_description( description.toUtf8() ); if ( isRich ) { icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) ); } return p; } icalproperty *ICalFormatImpl::writeSummary( const QString &summary, bool isRich ) { icalproperty *p = icalproperty_new_summary( summary.toUtf8() ); if ( isRich ) { icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) ); } return p; } icalproperty *ICalFormatImpl::writeLocation( const QString &location, bool isRich ) { icalproperty *p = icalproperty_new_location( location.toUtf8() ); if ( isRich ) { icalproperty_add_parameter( p, icalparameter_new_from_string( "X-KDE-TEXTFORMAT=HTML" ) ); } return p; } icalproperty *ICalFormatImpl::writeAttendee( Attendee *attendee ) { if ( attendee->email().isEmpty() ) { return 0; } icalproperty *p = icalproperty_new_attendee( "mailto:" + attendee->email().toUtf8() ); if ( !attendee->name().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_cn( quoteForParam( attendee->name() ).toUtf8() ) ); } icalproperty_add_parameter( p, icalparameter_new_rsvp( attendee->RSVP() ? ICAL_RSVP_TRUE : ICAL_RSVP_FALSE ) ); icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION; switch ( attendee->status() ) { default: case Attendee::NeedsAction: status = ICAL_PARTSTAT_NEEDSACTION; break; case Attendee::Accepted: status = ICAL_PARTSTAT_ACCEPTED; break; case Attendee::Declined: status = ICAL_PARTSTAT_DECLINED; break; case Attendee::Tentative: status = ICAL_PARTSTAT_TENTATIVE; break; case Attendee::Delegated: status = ICAL_PARTSTAT_DELEGATED; break; case Attendee::Completed: status = ICAL_PARTSTAT_COMPLETED; break; case Attendee::InProcess: status = ICAL_PARTSTAT_INPROCESS; break; } icalproperty_add_parameter( p, icalparameter_new_partstat( status ) ); icalparameter_role role = ICAL_ROLE_REQPARTICIPANT; switch ( attendee->role() ) { case Attendee::Chair: role = ICAL_ROLE_CHAIR; break; default: case Attendee::ReqParticipant: role = ICAL_ROLE_REQPARTICIPANT; break; case Attendee::OptParticipant: role = ICAL_ROLE_OPTPARTICIPANT; break; case Attendee::NonParticipant: role = ICAL_ROLE_NONPARTICIPANT; break; } icalproperty_add_parameter( p, icalparameter_new_role( role ) ); if ( !attendee->uid().isEmpty() ) { icalparameter *icalparameter_uid = icalparameter_new_x( attendee->uid().toUtf8() ); icalparameter_set_xname( icalparameter_uid, "X-UID" ); icalproperty_add_parameter( p, icalparameter_uid ); } if ( !attendee->delegate().isEmpty() ) { icalparameter *icalparameter_delegate = icalparameter_new_delegatedto( attendee->delegate().toUtf8() ); icalproperty_add_parameter( p, icalparameter_delegate ); } if ( !attendee->delegator().isEmpty() ) { icalparameter *icalparameter_delegator = icalparameter_new_delegatedfrom( attendee->delegator().toUtf8() ); icalproperty_add_parameter( p, icalparameter_delegator ); } return p; } icalproperty *ICalFormatImpl::writeAttachment( Attachment *att ) { icalattach *attach; if ( att->isUri() ) { attach = icalattach_new_from_url( att->uri().toUtf8().data() ); } else { attach = icalattach_new_from_data ( ( unsigned char * )att->data(), 0, 0 ); } icalproperty *p = icalproperty_new_attach( attach ); if ( !att->mimeType().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_fmttype( att->mimeType().toUtf8().data() ) ); } if ( att->isBinary() ) { icalproperty_add_parameter( p, icalparameter_new_value( ICAL_VALUE_BINARY ) ); icalproperty_add_parameter( p, icalparameter_new_encoding( ICAL_ENCODING_BASE64 ) ); } if ( att->showInline() ) { icalparameter *icalparameter_inline = icalparameter_new_x( "inline" ); icalparameter_set_xname( icalparameter_inline, "X-CONTENT-DISPOSITION" ); icalproperty_add_parameter( p, icalparameter_inline ); } if ( !att->label().isEmpty() ) { icalparameter *icalparameter_label = icalparameter_new_x( att->label().toUtf8() ); icalparameter_set_xname( icalparameter_label, "X-LABEL" ); icalproperty_add_parameter( p, icalparameter_label ); } if ( att->isLocal() ) { icalparameter *icalparameter_local = icalparameter_new_x( "local" ); icalparameter_set_xname( icalparameter_local, "X-KONTACT-TYPE" ); icalproperty_add_parameter( p, icalparameter_local ); } return p; } icalrecurrencetype ICalFormatImpl::writeRecurrenceRule( RecurrenceRule *recur ) { icalrecurrencetype r; icalrecurrencetype_clear( &r ); switch( recur->recurrenceType() ) { case RecurrenceRule::rSecondly: r.freq = ICAL_SECONDLY_RECURRENCE; break; case RecurrenceRule::rMinutely: r.freq = ICAL_MINUTELY_RECURRENCE; break; case RecurrenceRule::rHourly: r.freq = ICAL_HOURLY_RECURRENCE; break; case RecurrenceRule::rDaily: r.freq = ICAL_DAILY_RECURRENCE; break; case RecurrenceRule::rWeekly: r.freq = ICAL_WEEKLY_RECURRENCE; break; case RecurrenceRule::rMonthly: r.freq = ICAL_MONTHLY_RECURRENCE; break; case RecurrenceRule::rYearly: r.freq = ICAL_YEARLY_RECURRENCE; break; default: r.freq = ICAL_NO_RECURRENCE; kDebug() << "no recurrence"; break; } int index = 0; QList bys; QList::ConstIterator it; // Now write out the BY* parts: bys = recur->bySeconds(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_second[index++] = *it; } bys = recur->byMinutes(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_minute[index++] = *it; } bys = recur->byHours(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_hour[index++] = *it; } bys = recur->byMonthDays(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_month_day[index++] = icalrecurrencetype_day_position( (*it) * 8 ); } bys = recur->byYearDays(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_year_day[index++] = *it; } bys = recur->byWeekNumbers(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_week_no[index++] = *it; } bys = recur->byMonths(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_month[index++] = *it; } bys = recur->bySetPos(); index = 0; for ( it = bys.constBegin(); it != bys.constEnd(); ++it ) { r.by_set_pos[index++] = *it; } QList byd = recur->byDays(); int day; index = 0; for ( QList::ConstIterator dit = byd.constBegin(); dit != byd.constEnd(); ++dit ) { day = (*dit).day() % 7 + 1; // convert from Monday=1 to Sunday=1 if ( (*dit).pos() < 0 ) { day += ( -(*dit).pos() ) * 8; day = -day; } else { day += (*dit).pos() * 8; } r.by_day[index++] = day; } r.week_start = static_cast( recur->weekStart() % 7 + 1 ); if ( recur->frequency() > 1 ) { // Dont' write out INTERVAL=1, because that's the default anyway r.interval = recur->frequency(); } if ( recur->duration() > 0 ) { r.count = recur->duration(); } else if ( recur->duration() == -1 ) { r.count = 0; } else { if ( recur->allDay() ) { r.until = writeICalDate( recur->endDt().date() ); } else { r.until = writeICalUtcDateTime( recur->endDt() ); } } return r; } icalcomponent *ICalFormatImpl::writeAlarm( Alarm *alarm ) { icalcomponent *a = icalcomponent_new( ICAL_VALARM_COMPONENT ); icalproperty_action action; icalattach *attach = 0; switch ( alarm->type() ) { case Alarm::Procedure: action = ICAL_ACTION_PROCEDURE; attach = icalattach_new_from_url( QFile::encodeName( alarm->programFile() ).data() ); icalcomponent_add_property( a, icalproperty_new_attach( attach ) ); if ( !alarm->programArguments().isEmpty() ) { icalcomponent_add_property( a, icalproperty_new_description( alarm->programArguments().toUtf8() ) ); } break; case Alarm::Audio: action = ICAL_ACTION_AUDIO; if ( !alarm->audioFile().isEmpty() ) { attach = icalattach_new_from_url( QFile::encodeName( alarm->audioFile() ).data() ); icalcomponent_add_property( a, icalproperty_new_attach( attach ) ); } break; case Alarm::Email: { action = ICAL_ACTION_EMAIL; const QList addresses = alarm->mailAddresses(); for ( QList::ConstIterator ad = addresses.constBegin(); ad != addresses.constEnd(); ++ad ) { if ( !(*ad).email().isEmpty() ) { icalproperty *p = icalproperty_new_attendee( "MAILTO:" + (*ad).email().toUtf8() ); if ( !(*ad).name().isEmpty() ) { icalproperty_add_parameter( p, icalparameter_new_cn( quoteForParam( (*ad).name() ).toUtf8() ) ); } icalcomponent_add_property( a, p ); } } icalcomponent_add_property( a, icalproperty_new_summary( alarm->mailSubject().toUtf8() ) ); icalcomponent_add_property( a, icalproperty_new_description( alarm->mailText().toUtf8() ) ); QStringList attachments = alarm->mailAttachments(); if ( attachments.count() > 0 ) { for ( QStringList::const_iterator at = attachments.constBegin(); at != attachments.constEnd(); ++at ) { attach = icalattach_new_from_url( QFile::encodeName( *at ).data() ); icalcomponent_add_property( a, icalproperty_new_attach( attach ) ); } } break; } case Alarm::Display: action = ICAL_ACTION_DISPLAY; icalcomponent_add_property( a, icalproperty_new_description( alarm->text().toUtf8() ) ); break; case Alarm::Invalid: default: kDebug() << "Unknown type of alarm"; action = ICAL_ACTION_NONE; break; } icalcomponent_add_property( a, icalproperty_new_action( action ) ); // Trigger time icaltriggertype trigger; if ( alarm->hasTime() ) { trigger.time = writeICalUtcDateTime( alarm->time() ); trigger.duration = icaldurationtype_null_duration(); } else { trigger.time = icaltime_null_time(); Duration offset; if ( alarm->hasStartOffset() ) { offset = alarm->startOffset(); } else { offset = alarm->endOffset(); } trigger.duration = writeICalDuration( offset ); } icalproperty *p = icalproperty_new_trigger( trigger ); if ( alarm->hasEndOffset() ) { icalproperty_add_parameter( p, icalparameter_new_related( ICAL_RELATED_END ) ); } icalcomponent_add_property( a, p ); // Repeat count and duration if ( alarm->repeatCount() ) { icalcomponent_add_property( a, icalproperty_new_repeat( alarm->repeatCount() ) ); icalcomponent_add_property( a, icalproperty_new_duration( writeICalDuration( alarm->snoozeTime() ) ) ); } // Custom properties const QMap custom = alarm->customProperties(); for ( QMap::ConstIterator c = custom.begin(); c != custom.end(); ++c ) { icalproperty *p = icalproperty_new_x( c.value().toUtf8() ); icalproperty_set_x_name( p, c.key() ); icalcomponent_add_property( a, p ); } return a; } Todo *ICalFormatImpl::readTodo( icalcomponent *vtodo, ICalTimeZones *tzlist ) { Todo *todo = new Todo; readIncidence( vtodo, todo, tzlist ); icalproperty *p = icalcomponent_get_first_property( vtodo, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa(p); switch ( kind ) { case ICAL_DUE_PROPERTY: { // due date/time KDateTime kdt = readICalDateTimeProperty( p, tzlist ); todo->setDtDue( kdt, true ); todo->setHasDueDate( true ); todo->setAllDay( kdt.isDateOnly() ); break; } case ICAL_COMPLETED_PROPERTY: // completion date/time todo->setCompleted( readICalDateTimeProperty( p, tzlist ) ); break; case ICAL_PERCENTCOMPLETE_PROPERTY: // Percent completed todo->setPercentComplete( icalproperty_get_percentcomplete( p ) ); break; case ICAL_RELATEDTO_PROPERTY: // related todo (parent) todo->setRelatedToUid( QString::fromUtf8( icalproperty_get_relatedto( p ) ) ); d->mTodosRelate.append( todo ); break; case ICAL_DTSTART_PROPERTY: // Flag that todo has start date. Value is read in by readIncidence(). if ( todo->comments().filter( "NoStartDate" ).count() ) { todo->setHasStartDate( false ); } else { todo->setHasStartDate( true ); } break; case ICAL_RECURRENCEID_PROPERTY: todo->setDtRecurrence( readICalDateTimeProperty( p, tzlist ) ); break; default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( vtodo, ICAL_ANY_PROPERTY ); } if ( d->mCompat ) { d->mCompat->fixEmptySummary( todo ); } return todo; } Event *ICalFormatImpl::readEvent( icalcomponent *vevent, ICalTimeZones *tzlist ) { Event *event = new Event; readIncidence( vevent, event, tzlist ); icalproperty *p = icalcomponent_get_first_property( vevent, ICAL_ANY_PROPERTY ); bool dtEndProcessed = false; while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_DTEND_PROPERTY: { // end date and time KDateTime kdt = readICalDateTimeProperty( p, tzlist ); if ( kdt.isDateOnly() ) { // End date is non-inclusive QDate endDate = kdt.date().addDays( -1 ); if ( d->mCompat ) { d->mCompat->fixFloatingEnd( endDate ); } if ( endDate < event->dtStart().date() ) { endDate = event->dtStart().date(); } event->setDtEnd( KDateTime( endDate, event->dtStart().timeSpec() ) ); } else { event->setDtEnd( kdt ); event->setAllDay( false ); } dtEndProcessed = true; break; } case ICAL_RELATEDTO_PROPERTY: // related event (parent) event->setRelatedToUid( QString::fromUtf8( icalproperty_get_relatedto( p ) ) ); d->mEventsRelate.append( event ); break; case ICAL_TRANSP_PROPERTY: // Transparency { icalproperty_transp transparency = icalproperty_get_transp( p ); if ( transparency == ICAL_TRANSP_TRANSPARENT ) { event->setTransparency( Event::Transparent ); } else { event->setTransparency( Event::Opaque ); } break; } default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( vevent, ICAL_ANY_PROPERTY ); } // according to rfc2445 the dtend shouldn't be written when it equals // start date. so assign one equal to start date. if ( !dtEndProcessed && !event->hasDuration() ) { event->setDtEnd( event->dtStart() ); event->setHasEndDate( false ); } QString msade = event->nonKDECustomProperty( "X-MICROSOFT-CDO-ALLDAYEVENT" ); if ( !msade.isEmpty() ) { bool allDay = ( msade == QLatin1String( "TRUE" ) ); event->setAllDay( allDay ); } if ( d->mCompat ) { d->mCompat->fixEmptySummary( event ); } return event; } FreeBusy *ICalFormatImpl::readFreeBusy( icalcomponent *vfreebusy ) { FreeBusy *freebusy = new FreeBusy; d->readIncidenceBase( vfreebusy, freebusy ); icalproperty *p = icalcomponent_get_first_property( vfreebusy, ICAL_ANY_PROPERTY ); FreeBusyPeriod::List periods; while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_DTSTART_PROPERTY: // start date and time (UTC) freebusy->setDtStart( readICalUtcDateTimeProperty( p ) ); break; case ICAL_DTEND_PROPERTY: // end Date and Time (UTC) freebusy->setDtEnd( readICalUtcDateTimeProperty( p ) ); break; case ICAL_FREEBUSY_PROPERTY: //Any FreeBusy Times (UTC) { icalperiodtype icalperiod = icalproperty_get_freebusy( p ); KDateTime period_start = readICalUtcDateTime( p, icalperiod.start ); FreeBusyPeriod period; if ( !icaltime_is_null_time( icalperiod.end ) ) { KDateTime period_end = readICalUtcDateTime( p, icalperiod.end ); period = FreeBusyPeriod( period_start, period_end ); } else { Duration duration ( readICalDuration( icalperiod.duration ) ); period = FreeBusyPeriod( period_start, duration ); } QByteArray param = icalproperty_get_parameter_as_string( p, "X-SUMMARY" ); period.setSummary( QString::fromUtf8( KCodecs::base64Decode( param ) ) ); param = icalproperty_get_parameter_as_string( p, "X-LOCATION" ); period.setLocation( QString::fromUtf8( KCodecs::base64Decode( param ) ) ); periods.append( period ); break; } default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( vfreebusy, ICAL_ANY_PROPERTY ); } freebusy->addPeriods( periods ); return freebusy; } Journal *ICalFormatImpl::readJournal( icalcomponent *vjournal, ICalTimeZones *tzlist ) { Journal *journal = new Journal; readIncidence( vjournal, journal, tzlist ); return journal; } Attendee *ICalFormatImpl::readAttendee( icalproperty *attendee ) { // the following is a hack to support broken calendars (like WebCalendar 1.0.x) // that include non-RFC-compliant attendees. Otherwise libical 0.42 asserts. if ( !icalproperty_get_value( attendee ) ) { return 0; } icalparameter *p = 0; QString email = QString::fromUtf8( icalproperty_get_attendee( attendee ) ); if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { email = email.mid( 7 ); } QString name; QString uid; p = icalproperty_get_first_parameter( attendee, ICAL_CN_PARAMETER ); if ( p ) { name = QString::fromUtf8( icalparameter_get_cn( p ) ); } else { } bool rsvp = false; p = icalproperty_get_first_parameter( attendee, ICAL_RSVP_PARAMETER ); if ( p ) { icalparameter_rsvp rsvpParameter = icalparameter_get_rsvp( p ); if ( rsvpParameter == ICAL_RSVP_TRUE ) { rsvp = true; } } Attendee::PartStat status = Attendee::NeedsAction; p = icalproperty_get_first_parameter( attendee, ICAL_PARTSTAT_PARAMETER ); if ( p ) { icalparameter_partstat partStatParameter = icalparameter_get_partstat( p ); switch( partStatParameter ) { default: case ICAL_PARTSTAT_NEEDSACTION: status = Attendee::NeedsAction; break; case ICAL_PARTSTAT_ACCEPTED: status = Attendee::Accepted; break; case ICAL_PARTSTAT_DECLINED: status = Attendee::Declined; break; case ICAL_PARTSTAT_TENTATIVE: status = Attendee::Tentative; break; case ICAL_PARTSTAT_DELEGATED: status = Attendee::Delegated; break; case ICAL_PARTSTAT_COMPLETED: status = Attendee::Completed; break; case ICAL_PARTSTAT_INPROCESS: status = Attendee::InProcess; break; } } Attendee::Role role = Attendee::ReqParticipant; p = icalproperty_get_first_parameter( attendee, ICAL_ROLE_PARAMETER ); if ( p ) { icalparameter_role roleParameter = icalparameter_get_role( p ); switch( roleParameter ) { case ICAL_ROLE_CHAIR: role = Attendee::Chair; break; default: case ICAL_ROLE_REQPARTICIPANT: role = Attendee::ReqParticipant; break; case ICAL_ROLE_OPTPARTICIPANT: role = Attendee::OptParticipant; break; case ICAL_ROLE_NONPARTICIPANT: role = Attendee::NonParticipant; break; } } p = icalproperty_get_first_parameter( attendee, ICAL_X_PARAMETER ); while ( p ) { QString xname = QString( icalparameter_get_xname( p ) ).toUpper(); QString xvalue = QString::fromUtf8( icalparameter_get_xvalue( p ) ); if ( xname == "X-UID" ) { uid = xvalue; } p = icalproperty_get_next_parameter( attendee, ICAL_X_PARAMETER ); } Attendee *a = new Attendee( name, email, rsvp, status, role, uid ); p = icalproperty_get_first_parameter( attendee, ICAL_DELEGATEDTO_PARAMETER ); if ( p ) { a->setDelegate( icalparameter_get_delegatedto( p ) ); } p = icalproperty_get_first_parameter( attendee, ICAL_DELEGATEDFROM_PARAMETER ); if ( p ) { a->setDelegator( icalparameter_get_delegatedfrom( p ) ); } return a; } Person ICalFormatImpl::readOrganizer( icalproperty *organizer ) { QString email = QString::fromUtf8( icalproperty_get_organizer( organizer ) ); if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { email = email.mid( 7 ); } QString cn; icalparameter *p = icalproperty_get_first_parameter( organizer, ICAL_CN_PARAMETER ); if ( p ) { cn = QString::fromUtf8( icalparameter_get_cn( p ) ); } Person org( cn, email ); // TODO: Treat sent-by, dir and language here, too return org; } Attachment *ICalFormatImpl::readAttachment( icalproperty *attach ) { Attachment *attachment = 0; const char *p; icalvalue *value = icalproperty_get_value( attach ); switch( icalvalue_isa( value ) ) { case ICAL_ATTACH_VALUE: { icalattach *a = icalproperty_get_attach( attach ); if ( !icalattach_get_is_url( a ) ) { p = (const char *)icalattach_get_data( a ); if ( p ) { attachment = new Attachment( p ); } } else { p = icalattach_get_url( a ); if ( p ) { attachment = new Attachment( QString::fromUtf8( p ) ); } } break; } case ICAL_BINARY_VALUE: { icalattach *a = icalproperty_get_attach( attach ); p = (const char *)icalattach_get_data( a ); if ( p ) { attachment = new Attachment( p ); } break; } case ICAL_URI_VALUE: p = icalvalue_get_uri( value ); attachment = new Attachment( QString::fromUtf8( p ) ); break; default: break; } if ( attachment ) { icalparameter *p = icalproperty_get_first_parameter( attach, ICAL_FMTTYPE_PARAMETER ); if ( p ) { attachment->setMimeType( QString( icalparameter_get_fmttype( p ) ) ); } p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER ); while ( p ) { QString xname = QString( icalparameter_get_xname( p ) ).toUpper(); QString xvalue = QString::fromUtf8( icalparameter_get_xvalue( p ) ); if ( xname == "X-CONTENT-DISPOSITION" ) { attachment->setShowInline( xvalue.toLower() == "inline" ); } if ( xname == "X-LABEL" ) { attachment->setLabel( xvalue ); } if ( xname == "X-KONTACT-TYPE" ) { attachment->setLocal( xvalue.toLower() == "local" ); } p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER ); } p = icalproperty_get_first_parameter( attach, ICAL_X_PARAMETER ); while ( p ) { if ( strncmp ( icalparameter_get_xname( p ), "X-LABEL", 7 ) == 0 ) { attachment->setLabel( icalparameter_get_xvalue( p ) ); } p = icalproperty_get_next_parameter( attach, ICAL_X_PARAMETER ); } } return attachment; } void ICalFormatImpl::readIncidence( icalcomponent *parent, Incidence *incidence, ICalTimeZones *tzlist ) { d->readIncidenceBase( parent, incidence ); icalproperty *p = icalcomponent_get_first_property( parent, ICAL_ANY_PROPERTY ); const char *text; int intvalue, inttext; icaldurationtype icalduration; KDateTime kdt; QStringList categories; while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_CREATED_PROPERTY: incidence->setCreated( readICalDateTimeProperty( p, tzlist ) ); break; case ICAL_SEQUENCE_PROPERTY: // sequence intvalue = icalproperty_get_sequence( p ); incidence->setRevision( intvalue ); break; case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time incidence->setLastModified( readICalDateTimeProperty( p, tzlist ) ); break; case ICAL_DTSTART_PROPERTY: // start date and time kdt = readICalDateTimeProperty( p, tzlist ); incidence->setDtStart( kdt ); incidence->setAllDay( kdt.isDateOnly() ); break; case ICAL_DURATION_PROPERTY: // start date and time icalduration = icalproperty_get_duration( p ); incidence->setDuration( readICalDuration( icalduration ) ); break; case ICAL_DESCRIPTION_PROPERTY: // description { QString textStr = QString::fromUtf8( icalproperty_get_description( p ) ); if ( !textStr.isEmpty() ) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) ); if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) { incidence->setDescription( textStr, true ); } else { incidence->setDescription( textStr, false ); } } } break; case ICAL_SUMMARY_PROPERTY: // summary { QString textStr = QString::fromUtf8( icalproperty_get_summary( p ) ); if ( !textStr.isEmpty() ) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) ); if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) { incidence->setSummary( textStr, true ); } else { incidence->setSummary( textStr, false ); } } } break; case ICAL_LOCATION_PROPERTY: // location { if ( !icalproperty_get_value( p ) ) { //Fix for #191472. This is a pre-crash guard in case libical was //compiled in superstrict mode (--enable-icalerrors-are-fatal) //TODO: pre-crash guard other property getters too. break; } QString textStr = QString::fromUtf8( icalproperty_get_location( p ) ); if ( !textStr.isEmpty() ) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string( p, "X-KDE-TEXTFORMAT" ) ); if ( !valStr.compare( "HTML", Qt::CaseInsensitive ) ) { incidence->setLocation( textStr, true ); } else { incidence->setLocation( textStr, false ); } } } break; case ICAL_STATUS_PROPERTY: // status { Incidence::Status stat; switch ( icalproperty_get_status( p ) ) { case ICAL_STATUS_TENTATIVE: stat = Incidence::StatusTentative; break; case ICAL_STATUS_CONFIRMED: stat = Incidence::StatusConfirmed; break; case ICAL_STATUS_COMPLETED: stat = Incidence::StatusCompleted; break; case ICAL_STATUS_NEEDSACTION: stat = Incidence::StatusNeedsAction; break; case ICAL_STATUS_CANCELLED: stat = Incidence::StatusCanceled; break; case ICAL_STATUS_INPROCESS: stat = Incidence::StatusInProcess; break; case ICAL_STATUS_DRAFT: stat = Incidence::StatusDraft; break; case ICAL_STATUS_FINAL: stat = Incidence::StatusFinal; break; case ICAL_STATUS_X: incidence->setCustomStatus( QString::fromUtf8( icalvalue_get_x( icalproperty_get_value( p ) ) ) ); stat = Incidence::StatusX; break; case ICAL_STATUS_NONE: default: stat = Incidence::StatusNone; break; } if ( stat != Incidence::StatusX ) { incidence->setStatus( stat ); } break; } case ICAL_GEO_PROPERTY: // geo { icalgeotype geo = icalproperty_get_geo( p ); incidence->setGeoLatitude( geo.lat ); incidence->setGeoLongitude( geo.lon ); incidence->setHasGeo( true ); break; } case ICAL_PRIORITY_PROPERTY: // priority intvalue = icalproperty_get_priority( p ); if ( d->mCompat ) { intvalue = d->mCompat->fixPriority( intvalue ); } incidence->setPriority( intvalue ); break; case ICAL_CATEGORIES_PROPERTY: // categories { // We have always supported multiple CATEGORIES properties per component // even though the RFC seems to indicate only 1 is permitted. // We can't change that -- in order to retain backwards compatibility. text = icalproperty_get_categories( p ); const QString val = QString::fromUtf8( text ); foreach ( const QString &cat, val.split( ',', QString::SkipEmptyParts ) ) { // ensure no duplicates if ( !categories.contains( cat ) ) { categories.append( cat ); } } break; } case ICAL_RRULE_PROPERTY: readRecurrenceRule( p, incidence ); break; case ICAL_RDATE_PROPERTY: kdt = readICalDateTimeProperty( p, tzlist ); if ( kdt.isValid() ) { if ( kdt.isDateOnly() ) { incidence->recurrence()->addRDate( kdt.date() ); } else { incidence->recurrence()->addRDateTime( kdt ); } } else { // TODO: RDates as period are not yet implemented! } break; case ICAL_EXRULE_PROPERTY: readExceptionRule( p, incidence ); break; case ICAL_EXDATE_PROPERTY: kdt = readICalDateTimeProperty( p, tzlist ); if ( kdt.isDateOnly() ) { incidence->recurrence()->addExDate( kdt.date() ); } else { incidence->recurrence()->addExDateTime( kdt ); } break; case ICAL_CLASS_PROPERTY: inttext = icalproperty_get_class( p ); if ( inttext == ICAL_CLASS_PUBLIC ) { incidence->setSecrecy( Incidence::SecrecyPublic ); } else if ( inttext == ICAL_CLASS_CONFIDENTIAL ) { incidence->setSecrecy( Incidence::SecrecyConfidential ); } else { incidence->setSecrecy( Incidence::SecrecyPrivate ); } break; case ICAL_ATTACH_PROPERTY: // attachments incidence->addAttachment( readAttachment( p ) ); break; default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY ); } // Set the scheduling ID const QString uid = incidence->customProperty( "LIBKCAL", "ID" ); if ( !uid.isNull() ) { // The UID stored in incidencebase is actually the scheduling ID // It has to be stored in the iCal UID component for compatibility // with other iCal applications incidence->setSchedulingID( incidence->uid() ); incidence->setUid( uid ); } // Now that recurrence and exception stuff is completely set up, // do any backwards compatibility adjustments. if ( incidence->recurs() && d->mCompat ) { d->mCompat->fixRecurrence( incidence ); } // add categories incidence->setCategories( categories ); // iterate through all alarms for ( icalcomponent *alarm = icalcomponent_get_first_component( parent, ICAL_VALARM_COMPONENT ); alarm; alarm = icalcomponent_get_next_component( parent, ICAL_VALARM_COMPONENT ) ) { readAlarm( alarm, incidence, tzlist ); } // Fix incorrect alarm settings by other applications (like outloook 9) if ( d->mCompat ) { d->mCompat->fixAlarms( incidence ); } } //@cond PRIVATE void ICalFormatImpl::Private::readIncidenceBase( icalcomponent *parent, IncidenceBase *incidenceBase ) { icalproperty *p = icalcomponent_get_first_property( parent, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_UID_PROPERTY: // unique id incidenceBase->setUid( QString::fromUtf8( icalproperty_get_uid( p ) ) ); break; case ICAL_ORGANIZER_PROPERTY: // organizer incidenceBase->setOrganizer( mImpl->readOrganizer( p ) ); break; case ICAL_ATTENDEE_PROPERTY: // attendee incidenceBase->addAttendee( mImpl->readAttendee( p ) ); break; case ICAL_COMMENT_PROPERTY: incidenceBase->addComment( QString::fromUtf8( icalproperty_get_comment( p ) ) ); break; default: break; } p = icalcomponent_get_next_property( parent, ICAL_ANY_PROPERTY ); } // custom properties readCustomProperties( parent, incidenceBase ); } void ICalFormatImpl::Private::readCustomProperties( icalcomponent *parent, CustomProperties *properties ) { QMap customProperties; QString lastProperty; icalproperty *p = icalcomponent_get_first_property( parent, ICAL_X_PROPERTY ); while ( p ) { QString value = QString::fromUtf8( icalproperty_get_x( p ) ); const char *name = icalproperty_get_x_name( p ); if ( lastProperty != name ) { customProperties[name] = value; } else { customProperties[name] = customProperties[name].append( "," ).append( value ); } p = icalcomponent_get_next_property( parent, ICAL_X_PROPERTY ); lastProperty = name; } properties->setCustomProperties( customProperties ); } //@endcond void ICalFormatImpl::readRecurrenceRule( icalproperty *rrule, Incidence *incidence ) { Recurrence *recur = incidence->recurrence(); struct icalrecurrencetype r = icalproperty_get_rrule( rrule ); // dumpIcalRecurrence(r); RecurrenceRule *recurrule = new RecurrenceRule( /*incidence*/); recurrule->setStartDt( incidence->dtStart() ); readRecurrence( r, recurrule ); recur->addRRule( recurrule ); } void ICalFormatImpl::readExceptionRule( icalproperty *rrule, Incidence *incidence ) { struct icalrecurrencetype r = icalproperty_get_exrule( rrule ); // dumpIcalRecurrence(r); RecurrenceRule *recurrule = new RecurrenceRule( /*incidence*/); recurrule->setStartDt( incidence->dtStart() ); readRecurrence( r, recurrule ); Recurrence *recur = incidence->recurrence(); recur->addExRule( recurrule ); } void ICalFormatImpl::readRecurrence( const struct icalrecurrencetype &r, RecurrenceRule *recur ) { // Generate the RRULE string recur->setRRule( QString( icalrecurrencetype_as_string( const_cast( &r ) ) ) ); // Period switch ( r.freq ) { case ICAL_SECONDLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rSecondly ); break; case ICAL_MINUTELY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rMinutely ); break; case ICAL_HOURLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rHourly ); break; case ICAL_DAILY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rDaily ); break; case ICAL_WEEKLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rWeekly ); break; case ICAL_MONTHLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rMonthly ); break; case ICAL_YEARLY_RECURRENCE: recur->setRecurrenceType( RecurrenceRule::rYearly ); break; case ICAL_NO_RECURRENCE: default: recur->setRecurrenceType( RecurrenceRule::rNone ); } // Frequency recur->setFrequency( r.interval ); // Duration & End Date if ( !icaltime_is_null_time( r.until ) ) { icaltimetype t = r.until; recur->setEndDt( readICalUtcDateTime( 0, t ) ); } else { if ( r.count == 0 ) { recur->setDuration( -1 ); } else { recur->setDuration( r.count ); } } // Week start setting int wkst = ( r.week_start + 5 ) % 7 + 1; recur->setWeekStart( wkst ); // And now all BY* QList lst; int i; int index = 0; //@cond PRIVATE #define readSetByList( rrulecomp, setfunc ) \ index = 0; \ lst.clear(); \ while ( ( i = r.rrulecomp[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { \ lst.append( i ); \ } \ if ( !lst.isEmpty() ) { \ recur->setfunc( lst ); \ } //@endcond // BYSECOND, MINUTE and HOUR, MONTHDAY, YEARDAY, WEEKNUMBER, MONTH // and SETPOS are standard int lists, so we can treat them with the // same macro readSetByList( by_second, setBySeconds ); readSetByList( by_minute, setByMinutes ); readSetByList( by_hour, setByHours ); readSetByList( by_month_day, setByMonthDays ); readSetByList( by_year_day, setByYearDays ); readSetByList( by_week_no, setByWeekNumbers ); readSetByList( by_month, setByMonths ); readSetByList( by_set_pos, setBySetPos ); #undef readSetByList // BYDAY is a special case, since it's not an int list QList wdlst; short day; index=0; while ( ( day = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { RecurrenceRule::WDayPos pos; pos.setDay( ( icalrecurrencetype_day_day_of_week( day ) + 5 ) % 7 + 1 ); pos.setPos( icalrecurrencetype_day_position( day ) ); wdlst.append( pos ); } if ( !wdlst.isEmpty() ) { recur->setByDays( wdlst ); } // TODO: Store all X- fields of the RRULE inside the recurrence (so they are // preserved } void ICalFormatImpl::readAlarm( icalcomponent *alarm, Incidence *incidence, ICalTimeZones *tzlist ) { Alarm *ialarm = incidence->newAlarm(); ialarm->setRepeatCount( 0 ); ialarm->setEnabled( true ); // Determine the alarm's action type icalproperty *p = icalcomponent_get_first_property( alarm, ICAL_ACTION_PROPERTY ); Alarm::Type type = Alarm::Display; icalproperty_action action = ICAL_ACTION_DISPLAY; if ( !p ) { kDebug() << "Unknown type of alarm, using default"; // TODO: do something about unknown alarm type? } else { action = icalproperty_get_action( p ); switch ( action ) { case ICAL_ACTION_DISPLAY: type = Alarm::Display; break; case ICAL_ACTION_AUDIO: type = Alarm::Audio; break; case ICAL_ACTION_PROCEDURE: type = Alarm::Procedure; break; case ICAL_ACTION_EMAIL: type = Alarm::Email; break; default: break; // TODO: do something about invalid alarm type? } } ialarm->setType( type ); p = icalcomponent_get_first_property( alarm, ICAL_ANY_PROPERTY ); while ( p ) { icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_TRIGGER_PROPERTY: { icaltriggertype trigger = icalproperty_get_trigger( p ); if ( !icaltime_is_null_time( trigger.time ) ) { //set the trigger to a specific time (which is not in rfc2445, btw) ialarm->setTime( readICalUtcDateTime( p, trigger.time, tzlist ) ); } else { //set the trigger to an offset from the incidence start or end time. if ( !icaldurationtype_is_bad_duration( trigger.duration ) ) { Duration duration( readICalDuration( trigger.duration ) ); icalparameter *param = icalproperty_get_first_parameter( p, ICAL_RELATED_PARAMETER ); if ( param && icalparameter_get_related( param ) == ICAL_RELATED_END ) { ialarm->setEndOffset( duration ); } else { ialarm->setStartOffset( duration ); } } else { // a bad duration was encountered, just set a 0 duration from start ialarm->setStartOffset( Duration( 0 ) ); } } break; } case ICAL_DURATION_PROPERTY: { icaldurationtype duration = icalproperty_get_duration( p ); ialarm->setSnoozeTime( readICalDuration( duration ) ); break; } case ICAL_REPEAT_PROPERTY: ialarm->setRepeatCount( icalproperty_get_repeat( p ) ); break; case ICAL_DESCRIPTION_PROPERTY: { // Only in DISPLAY and EMAIL and PROCEDURE alarms QString description = QString::fromUtf8( icalproperty_get_description( p ) ); switch ( action ) { case ICAL_ACTION_DISPLAY: ialarm->setText( description ); break; case ICAL_ACTION_PROCEDURE: ialarm->setProgramArguments( description ); break; case ICAL_ACTION_EMAIL: ialarm->setMailText( description ); break; default: break; } break; } case ICAL_SUMMARY_PROPERTY: // Only in EMAIL alarm ialarm->setMailSubject( QString::fromUtf8( icalproperty_get_summary( p ) ) ); break; case ICAL_ATTENDEE_PROPERTY: { // Only in EMAIL alarm QString email = QString::fromUtf8( icalproperty_get_attendee( p ) ); if ( email.startsWith( QLatin1String( "mailto:" ), Qt::CaseInsensitive ) ) { email = email.mid( 7 ); } QString name; icalparameter *param = icalproperty_get_first_parameter( p, ICAL_CN_PARAMETER ); if ( param ) { name = QString::fromUtf8( icalparameter_get_cn( param ) ); } ialarm->addMailAddress( Person( name, email ) ); break; } case ICAL_ATTACH_PROPERTY: { // Only in AUDIO and EMAIL and PROCEDURE alarms Attachment *attach = readAttachment( p ); if ( attach && attach->isUri() ) { switch ( action ) { case ICAL_ACTION_AUDIO: ialarm->setAudioFile( attach->uri() ); break; case ICAL_ACTION_PROCEDURE: ialarm->setProgramFile( attach->uri() ); break; case ICAL_ACTION_EMAIL: ialarm->addMailAttachment( attach->uri() ); break; default: break; } } else { kDebug() << "Alarm attachments currently only support URIs," << "but no binary data"; } delete attach; break; } default: break; } p = icalcomponent_get_next_property( alarm, ICAL_ANY_PROPERTY ); } // custom properties d->readCustomProperties( alarm, ialarm ); // TODO: check for consistency of alarm properties } icaldatetimeperiodtype ICalFormatImpl::writeICalDatePeriod( const QDate &date ) { icaldatetimeperiodtype t; t.time = writeICalDate( date ); t.period = icalperiodtype_null_period(); return t; } icaltimetype ICalFormatImpl::writeICalDate( const QDate &date ) { icaltimetype t = icaltime_null_time(); t.year = date.year(); t.month = date.month(); t.day = date.day(); t.hour = 0; t.minute = 0; t.second = 0; t.is_date = 1; t.is_utc = 0; t.zone = 0; return t; } icaltimetype ICalFormatImpl::writeICalDateTime( const KDateTime &datetime ) { icaltimetype t = icaltime_null_time(); t.year = datetime.date().year(); t.month = datetime.date().month(); t.day = datetime.date().day(); t.hour = datetime.time().hour(); t.minute = datetime.time().minute(); t.second = datetime.time().second(); t.is_date = 0; t.zone = 0; // zone is NOT set t.is_utc = datetime.isUtc() ? 1 : 0; // _dumpIcaltime( t ); return t; } icalproperty *ICalFormatImpl::writeICalDateTimeProperty( const icalproperty_kind type, const KDateTime &dt, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList ) { icaltimetype t; switch ( type ) { case ICAL_DTSTAMP_PROPERTY: case ICAL_CREATED_PROPERTY: case ICAL_LASTMODIFIED_PROPERTY: t = writeICalDateTime( dt.toUtc() ); break; default: t = writeICalDateTime( dt ); break; } icalproperty *p; switch ( type ) { case ICAL_DTSTAMP_PROPERTY: p = icalproperty_new_dtstamp( t ); break; case ICAL_CREATED_PROPERTY: p = icalproperty_new_created( t ); break; case ICAL_LASTMODIFIED_PROPERTY: p = icalproperty_new_lastmodified( t ); break; case ICAL_DTSTART_PROPERTY: // start date and time p = icalproperty_new_dtstart( t ); break; case ICAL_DTEND_PROPERTY: // end date and time p = icalproperty_new_dtend( t ); break; case ICAL_DUE_PROPERTY: p = icalproperty_new_due( t ); break; case ICAL_RECURRENCEID_PROPERTY: p = icalproperty_new_recurrenceid( t ); break; case ICAL_EXDATE_PROPERTY: p = icalproperty_new_exdate( t ); break; default: { icaldatetimeperiodtype tp; tp.time = t; tp.period = icalperiodtype_null_period(); switch ( type ) { case ICAL_RDATE_PROPERTY: p = icalproperty_new_rdate( tp ); break; default: return 0; } } } KTimeZone ktz; if ( !t.is_utc ) { ktz = dt.timeZone(); } if ( ktz.isValid() ) { if ( tzlist ) { ICalTimeZone tz = tzlist->zone( ktz.name() ); if ( !tz.isValid() ) { // The time zone isn't in the list of known zones for the calendar // - add it to the calendar's zone list ICalTimeZone tznew( ktz ); tzlist->add( tznew ); tz = tznew; } if ( tzUsedList ) { tzUsedList->add( tz ); } } icalproperty_add_parameter( p, icalparameter_new_tzid( ktz.name().toUtf8() ) ); } return p; } KDateTime ICalFormatImpl::readICalDateTime( icalproperty *p, const icaltimetype &t, ICalTimeZones *tzlist, bool utc ) { // kDebug(); // _dumpIcaltime( t ); KDateTime::Spec timeSpec; if ( t.is_utc || t.zone == icaltimezone_get_utc_timezone() ) { timeSpec = KDateTime::UTC; // the time zone is UTC utc = false; // no need to convert to UTC } else { if ( !tzlist ) { utc = true; // should be UTC, but it isn't } icalparameter *param = p ? icalproperty_get_first_parameter( p, ICAL_TZID_PARAMETER ) : 0; const char *tzid = param ? icalparameter_get_tzid( param ) : 0; if ( !tzid ) { timeSpec = KDateTime::ClockTime; } else { QString tzidStr = QString::fromUtf8( tzid ); ICalTimeZone tz; if ( tzlist ) { tz = tzlist->zone( tzidStr ); } if ( !tz.isValid() ) { // The time zone is not in the existing list for the calendar. // Try to read it from the system or libical databases. ICalTimeZoneSource tzsource; ICalTimeZone newtz = tzsource.standardZone( tzidStr ); if ( newtz.isValid() && tzlist ) { tzlist->add( newtz ); } tz = newtz; } timeSpec = tz.isValid() ? KDateTime::Spec( tz ) : KDateTime::LocalZone; } } KDateTime result( QDate( t.year, t.month, t.day ), QTime( t.hour, t.minute, t.second ), timeSpec ); return utc ? result.toUtc() : result; } QDate ICalFormatImpl::readICalDate( icaltimetype t ) { return QDate( t.year, t.month, t.day ); } KDateTime ICalFormatImpl::readICalDateTimeProperty( icalproperty *p, ICalTimeZones *tzlist, bool utc ) { icaldatetimeperiodtype tp; icalproperty_kind kind = icalproperty_isa( p ); switch ( kind ) { case ICAL_CREATED_PROPERTY: // UTC date/time tp.time = icalproperty_get_created( p ); utc = true; break; case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time tp.time = icalproperty_get_lastmodified( p ); utc = true; break; case ICAL_DTSTART_PROPERTY: // start date and time (UTC for freebusy) tp.time = icalproperty_get_dtstart( p ); break; case ICAL_DTEND_PROPERTY: // end date and time (UTC for freebusy) tp.time = icalproperty_get_dtend( p ); break; case ICAL_DUE_PROPERTY: // due date/time tp.time = icalproperty_get_due( p ); break; case ICAL_COMPLETED_PROPERTY: // UTC completion date/time tp.time = icalproperty_get_completed( p ); utc = true; break; case ICAL_RECURRENCEID_PROPERTY: tp.time = icalproperty_get_recurrenceid( p ); break; case ICAL_EXDATE_PROPERTY: tp.time = icalproperty_get_exdate( p ); break; default: switch ( kind ) { case ICAL_RDATE_PROPERTY: tp = icalproperty_get_rdate( p ); break; default: return KDateTime(); } if ( !icaltime_is_valid_time( tp.time ) ) { return KDateTime(); // a time period was found (not implemented yet) } break; } if ( tp.time.is_date ) { return KDateTime( readICalDate( tp.time ), KDateTime::Spec::ClockTime() ); } else { return readICalDateTime( p, tp.time, tzlist, utc ); } } icaldurationtype ICalFormatImpl::writeICalDuration( const Duration &duration ) { // should be able to use icaldurationtype_from_int(), except we know // that some older tools do not properly support weeks. So we never // set a week duration, only days icaldurationtype d; int value = duration.value(); d.is_neg = ( value < 0 ) ? 1 : 0; if ( value < 0 ) { value = -value; } if ( duration.isDaily() ) { d.weeks = 0; d.days = value; d.hours = d.minutes = d.seconds = 0; } else { d.weeks = 0; d.days = value / gSecondsPerDay; value %= gSecondsPerDay; d.hours = value / gSecondsPerHour; value %= gSecondsPerHour; d.minutes = value / gSecondsPerMinute; value %= gSecondsPerMinute; d.seconds = value; } return d; } Duration ICalFormatImpl::readICalDuration( icaldurationtype d ) { int days = d.weeks * 7; days += d.days; int seconds = d.hours * gSecondsPerHour; seconds += d.minutes * gSecondsPerMinute; seconds += d.seconds; if ( seconds ) { seconds += days * gSecondsPerDay; if ( d.is_neg ) { seconds = -seconds; } return Duration( seconds, Duration::Seconds ); } else { if ( d.is_neg ) { days = -days; } return Duration( days, Duration::Days ); } } icalcomponent *ICalFormatImpl::createCalendarComponent( Calendar *cal ) { icalcomponent *calendar; // Root component calendar = icalcomponent_new( ICAL_VCALENDAR_COMPONENT ); icalproperty *p; // Product Identifier p = icalproperty_new_prodid( CalFormat::productId().toUtf8() ); icalcomponent_add_property( calendar, p ); // iCalendar version (2.0) p = icalproperty_new_version( const_cast(_ICAL_VERSION) ); icalcomponent_add_property( calendar, p ); // Add time zone if ( cal && cal->timeZones() ) { const ICalTimeZones::ZoneMap zmaps = cal->timeZones()->zones(); for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin(); it != zmaps.constEnd(); ++it ) { icaltimezone *icaltz = (*it).icalTimezone(); if ( !icaltz ) { kError() << "bad time zone"; } else { icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) ); icalcomponent_add_component( calendar, tz ); icaltimezone_free( icaltz, 1 ); } } } // Custom properties if( cal != 0 ) { d->writeCustomProperties( calendar, cal ); } return calendar; } // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc. // and break it down from its tree-like format into the dictionary format // that is used internally in the ICalFormatImpl. bool ICalFormatImpl::populate( Calendar *cal, icalcomponent *calendar ) { // this function will populate the caldict dictionary and other event // lists. It turns vevents into Events and then inserts them. if ( !calendar ) { return false; } // TODO: check for METHOD icalproperty *p; p = icalcomponent_get_first_property( calendar, ICAL_PRODID_PROPERTY ); if ( !p ) { kDebug() << "No PRODID property found"; d->mLoadedProductId = ""; } else { d->mLoadedProductId = QString::fromUtf8( icalproperty_get_prodid( p ) ); delete d->mCompat; d->mCompat = CompatFactory::createCompat( d->mLoadedProductId ); } p = icalcomponent_get_first_property( calendar, ICAL_VERSION_PROPERTY ); if ( !p ) { kDebug() << "No VERSION property found"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersionUnknown ) ); return false; } else { const char *version = icalproperty_get_version( p ); if ( !version ) { kDebug() << "No VERSION property found"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersionUnknown, i18n( "No VERSION property found" ) ) ); return false; } if ( strcmp( version, "1.0" ) == 0 ) { kDebug() << "Expected iCalendar, got vCalendar"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersion1, i18n( "Expected iCalendar, got vCalendar format" ) ) ); return false; } else if ( strcmp( version, "2.0" ) != 0 ) { kDebug() << "Expected iCalendar, got unknown format"; d->mParent->setException( new ErrorFormat( ErrorFormat::CalVersionUnknown, i18n( "Expected iCalendar, got unknown format" ) ) ); return false; } } // Populate the calendar's time zone collection with all VTIMEZONE components ICalTimeZones *tzlist = cal->timeZones(); ICalTimeZoneSource tzs; tzs.parse( calendar, *tzlist ); // custom properties d->readCustomProperties( calendar, cal ); // Store all events with a relatedTo property in a list for post-processing d->mEventsRelate.clear(); d->mTodosRelate.clear(); // TODO: make sure that only actually added events go to this lists. icalcomponent *c; // Iterate through all todos cal->beginBatchAdding(); c = icalcomponent_get_first_component( calendar, ICAL_VTODO_COMPONENT ); while ( c ) { Todo *todo = readTodo( c, tzlist ); if ( todo ) { Todo *old = cal->todo( todo->uid() ); if ( old ) { cal->deleteTodo( old ); d->mTodosRelate.removeAll( old ); } cal->addTodo( todo ); } c = icalcomponent_get_next_component( calendar, ICAL_VTODO_COMPONENT ); } // Iterate through all events c = icalcomponent_get_first_component( calendar, ICAL_VEVENT_COMPONENT ); while ( c ) { Event *event = readEvent( c, tzlist ); if ( event ) { Event *old = cal->event( event->uid() ); if ( old ) { cal->deleteEvent( old ); d->mEventsRelate.removeAll( old ); } cal->addEvent( event ); } c = icalcomponent_get_next_component( calendar, ICAL_VEVENT_COMPONENT ); } // Iterate through all journals c = icalcomponent_get_first_component( calendar, ICAL_VJOURNAL_COMPONENT ); while ( c ) { Journal *journal = readJournal( c, tzlist ); if ( journal ) { Journal *old = cal->journal( journal->uid() ); if ( old ) { cal->deleteJournal( old ); } cal->addJournal( journal ); } c = icalcomponent_get_next_component( calendar, ICAL_VJOURNAL_COMPONENT ); } cal->endBatchAdding(); // Post-Process list of events with relations, put Event objects in relation Event::List::ConstIterator eIt; for ( eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt ) { (*eIt)->setRelatedTo( cal->incidence( (*eIt)->relatedToUid() ) ); } Todo::List::ConstIterator tIt; for ( tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt ) { (*tIt)->setRelatedTo( cal->incidence( (*tIt)->relatedToUid() ) ); } // TODO: Remove any previous time zones no longer referenced in the calendar return true; } QString ICalFormatImpl::extractErrorProperty( icalcomponent *c ) { QString errorMessage; icalproperty *error; error = icalcomponent_get_first_property( c, ICAL_XLICERROR_PROPERTY ); while ( error ) { errorMessage += icalproperty_get_xlicerror( error ); errorMessage += '\n'; error = icalcomponent_get_next_property( c, ICAL_XLICERROR_PROPERTY ); } return errorMessage; } void ICalFormatImpl::dumpIcalRecurrence( icalrecurrencetype r ) { int i; kDebug() << " Freq:" << r.freq; kDebug() << " Until:" << icaltime_as_ical_string( r.until ); kDebug() << " Count:" << r.count; if ( r.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Day: "; while ( ( i = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Month Day: "; while ( ( i = r.by_month_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Year Day: "; while ( ( i = r.by_year_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Month: "; while ( ( i = r.by_month[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } kDebug() << out; } if ( r.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Set Pos: "; while ( ( i = r.by_set_pos[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { kDebug() << "=========" << i; out.append( QString::number( i ) + ' ' ); } kDebug() << out; } } icalcomponent *ICalFormatImpl::createScheduleComponent( IncidenceBase *incidence, iTIPMethod method ) { icalcomponent *message = createCalendarComponent(); // Create VTIMEZONE components for this incidence ICalTimeZones zones; if ( incidence ) { if ( incidence->type() == "Event" ) { Event *ev = static_cast( incidence ); if ( ev ) { if ( ev->dtStart().isValid() ) { zones.add( ICalTimeZone( ev->dtStart().timeZone() ) ); } if ( ev->hasEndDate() && ev->dtEnd().isValid() ) { zones.add( ICalTimeZone( ev->dtEnd().timeZone() ) ); } } } else if ( incidence->type() == "Todo" ) { Todo *t = static_cast( incidence ); if ( t ) { if ( t->hasStartDate() && t->dtStart().isValid() ) { zones.add( ICalTimeZone( t->dtStart( true ).timeZone() ) ); } if ( t->hasDueDate() && t->dtDue().isValid() ) { zones.add( ICalTimeZone( t->dtDue().timeZone() ) ); } } } else if ( incidence->type() == "Journal" ) { Journal *j = static_cast( incidence ); if ( j ) { if ( j->dtStart().isValid() ) { zones.add( ICalTimeZone( j->dtStart().timeZone() ) ); } } } const ICalTimeZones::ZoneMap zmaps = zones.zones(); for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin(); it != zmaps.constEnd(); ++it ) { icaltimezone *icaltz = (*it).icalTimezone(); if ( !icaltz ) { kError() << "bad time zone"; } else { icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) ); icalcomponent_add_component( message, tz ); icaltimezone_free( icaltz, 1 ); } } } icalproperty_method icalmethod = ICAL_METHOD_NONE; switch (method) { case iTIPPublish: icalmethod = ICAL_METHOD_PUBLISH; break; case iTIPRequest: icalmethod = ICAL_METHOD_REQUEST; break; case iTIPRefresh: icalmethod = ICAL_METHOD_REFRESH; break; case iTIPCancel: icalmethod = ICAL_METHOD_CANCEL; break; case iTIPAdd: icalmethod = ICAL_METHOD_ADD; break; case iTIPReply: icalmethod = ICAL_METHOD_REPLY; break; case iTIPCounter: icalmethod = ICAL_METHOD_COUNTER; break; case iTIPDeclineCounter: icalmethod = ICAL_METHOD_DECLINECOUNTER; break; default: kDebug() << "Unknown method"; return message; } icalcomponent_add_property( message, icalproperty_new_method( icalmethod ) ); icalcomponent *inc = writeIncidence( incidence, method ); /* * RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that * a REQUEST-STATUS property has to be present. For the other two, event and * free busy, it can be there, but is optional. Until we do more * fine grained handling, assume all is well. Note that this is the * status of the _request_, not the attendee. Just to avoid confusion. * - till */ if ( icalmethod == ICAL_METHOD_REPLY ) { struct icalreqstattype rst; rst.code = ICAL_2_0_SUCCESS_STATUS; rst.desc = 0; rst.debug = 0; icalcomponent_add_property( inc, icalproperty_new_requeststatus( rst ) ); } icalcomponent_add_component( message, inc ); return message; } diff --git a/kcal/incidenceformatter.cpp b/kcal/incidenceformatter.cpp index 754a99533..f0c7905c5 100644 --- a/kcal/incidenceformatter.cpp +++ b/kcal/incidenceformatter.cpp @@ -1,2788 +1,2835 @@ /* This file is part of the kcal library. Copyright (c) 2001 Cornelius Schumacher Copyright (c) 2004 Reinhold Kainhofer Copyright (c) 2005 Rafal Rzepecki Copyright (c) 2009 Klarไlvdalens Datakonsult AB, a KDAB Group company 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 API for handling calendar data and provides static functions for formatting Incidences for various purposes. @brief Provides methods to format Incidences in various ways for display purposes. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ */ #include "incidenceformatter.h" #include "attachment.h" #include "event.h" #include "todo.h" #include "journal.h" #include "calendar.h" #include "calendarlocal.h" #include "icalformat.h" #include "freebusy.h" #include "calendarresources.h" #include "kpimutils/email.h" #include "kabc/phonenumber.h" #include "kabc/vcardconverter.h" #include "kabc/stdaddressbook.h" #include #include + #include #include #include #include #include +#include #include #include #include #include #include using namespace KCal; /******************************************************************* * Helper functions for the extensive display (event viewer) *******************************************************************/ //@cond PRIVATE static QString eventViewerAddLink( const QString &ref, const QString &text, bool newline = true ) { QString tmpStr( "" + text + "" ); if ( newline ) { tmpStr += '\n'; } return tmpStr; } static QString eventViewerAddTag( const QString &tag, const QString &text ) { int numLineBreaks = text.count( "\n" ); QString str = '<' + tag + '>'; QString tmpText = text; QString tmpStr = str; if( numLineBreaks >= 0 ) { if ( numLineBreaks > 0 ) { int pos = 0; QString tmp; for ( int i = 0; i <= numLineBreaks; ++i ) { pos = tmpText.indexOf( "\n" ); tmp = tmpText.left( pos ); tmpText = tmpText.right( tmpText.length() - pos - 1 ); tmpStr += tmp + "
"; } } else { tmpStr += tmpText; } } tmpStr += "'; return tmpStr; } static QString eventViewerFormatCategories( Incidence *event ) { QString tmpStr; if ( !event->categoriesStr().isEmpty() ) { if ( event->categories().count() == 1 ) { tmpStr = eventViewerAddTag( "h3", i18n( "Category" ) ); } else { tmpStr = eventViewerAddTag( "h3", i18n( "Categories" ) ); } tmpStr += eventViewerAddTag( "p", event->categoriesStr() ); } return tmpStr; } static QString linkPerson( const QString &email, QString name, QString uid, const QString &iconPath ) { // Make the search, if there is an email address to search on, // and either name or uid is missing if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) { KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); KABC::Addressee::List addressList = add_book->findByEmail( email ); KABC::Addressee o = ( !addressList.isEmpty() ? addressList.first() : KABC::Addressee() ); if ( !o.isEmpty() && addressList.size() < 2 ) { if ( name.isEmpty() ) { // No name set, so use the one from the addressbook name = o.formattedName(); } uid = o.uid(); } else { // Email not found in the addressbook. Don't make a link uid.clear(); } } // Show the attendee QString tmpString = "
  • "; if ( !uid.isEmpty() ) { // There is a UID, so make a link to the addressbook if ( name.isEmpty() ) { // Use the email address for text tmpString += eventViewerAddLink( "uid:" + uid, email ); } else { tmpString += eventViewerAddLink( "uid:" + uid, name ); } } else { // No UID, just show some text tmpString += ( name.isEmpty() ? email : name ); } tmpString += '\n'; // Make the mailto link if ( !email.isEmpty() && !iconPath.isNull() ) { KUrl mailto; mailto.setProtocol( "mailto" ); mailto.setPath( email ); tmpString += eventViewerAddLink( mailto.url(), "" ); } tmpString += "
  • \n"; return tmpString; } static QString eventViewerFormatAttendees( Incidence *event ) { QString tmpStr; Attendee::List attendees = event->attendees(); if ( attendees.count() ) { KIconLoader *iconLoader = KIconLoader::global(); const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small ); // Add organizer link tmpStr += eventViewerAddTag( "h4", i18n( "Organizer" ) ); tmpStr += "
      "; tmpStr += linkPerson( event->organizer().email(), event->organizer().name(), QString(), iconPath ); tmpStr += "
    "; // Add attendees links tmpStr += eventViewerAddTag( "h4", i18n( "Attendees" ) ); tmpStr += "
      "; Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; tmpStr += linkPerson( a->email(), a->name(), a->uid(), iconPath ); if ( !a->delegator().isEmpty() ) { tmpStr += i18n( " (delegated by %1)", a->delegator() ); } if ( !a->delegate().isEmpty() ) { tmpStr += i18n( " (delegated to %1)", a->delegate() ); } } tmpStr += "
    "; } return tmpStr; } static QString eventViewerFormatAttachments( Incidence *i ) { QString tmpStr; Attachment::List as = i->attachments(); if ( as.count() > 0 ) { Attachment::List::ConstIterator it; for ( it = as.constBegin(); it != as.constEnd(); ++it ) { if ( (*it)->isUri() ) { tmpStr += eventViewerAddLink( (*it)->uri(), (*it)->label() ); tmpStr += "
    "; } } } return tmpStr; } /* FIXME:This function depends of kaddressbook. Is necessary a new type of event? */ static QString eventViewerFormatBirthday( Event *event ) { if ( !event ) { return QString(); } if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" ) { return QString(); } QString uid_1 = event->customProperty( "KABC", "UID-1" ); QString name_1 = event->customProperty( "KABC", "NAME-1" ); QString email_1= event->customProperty( "KABC", "EMAIL-1" ); KIconLoader *iconLoader = KIconLoader::global(); const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small ); //TODO: add a tart icon QString tmpString = "
      "; tmpString += linkPerson( email_1, name_1, uid_1, iconPath ); if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) { QString uid_2 = event->customProperty( "KABC", "UID-2" ); QString name_2 = event->customProperty( "KABC", "NAME-2" ); QString email_2= event->customProperty( "KABC", "EMAIL-2" ); tmpString += linkPerson( email_2, name_2, uid_2, iconPath ); } tmpString += "
    "; return tmpString; } static QString eventViewerFormatHeader( Incidence *incidence ) { QString tmpStr = ""; // show icons KIconLoader *iconLoader = KIconLoader::global(); tmpStr += ""; tmpStr += ""; tmpStr += "
    "; // TODO: KDE5. Make the function QString Incidence::getPixmap() so we don't // need downcasting. if ( incidence->type() == "Todo" ) { tmpStr += "( incidence ); if ( !todo->isCompleted() ) { tmpStr += iconLoader->iconPath( "view-calendar-tasks", KIconLoader::Small ); } else { tmpStr += iconLoader->iconPath( "task-complete", KIconLoader::Small ); } tmpStr += "\">"; } if ( incidence->type() == "Event" ) { tmpStr += "iconPath( "view-calendar-day", KIconLoader::Small ) + "\">"; } if ( incidence->type() == "Journal" ) { tmpStr += "iconPath( "view-pim-journal", KIconLoader::Small ) + "\">"; } if ( incidence->isAlarmEnabled() ) { tmpStr += "iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) + "\">"; } if ( incidence->recurs() ) { tmpStr += "iconPath( "edit-redo", KIconLoader::Small ) + "\">"; } if ( incidence->isReadOnly() ) { tmpStr += "iconPath( "object-locked", KIconLoader::Small ) + "\">"; } tmpStr += "" + eventViewerAddTag( "h2", incidence->richSummary() ) + "
    "; return tmpStr; } static QString eventViewerFormatEvent( Event *event, KDateTime::Spec spec ) { if ( !event ) { return QString(); } QString tmpStr = eventViewerFormatHeader( event ); tmpStr += ""; if ( !event->location().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += ""; if ( event->allDay() ) { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; tmpStr += ""; } } else { if ( event->isMultiDay() ) { tmpStr += ""; tmpStr += ""; } else { tmpStr += ""; if ( event->hasEndDate() && event->dtStart() != event->dtEnd() ) { tmpStr += ""; } else { tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += ""; } } tmpStr += ""; if ( event->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += "
    " + i18n( "Location" ) + "" + event->richLocation() + "
    " + i18n( "Time" ) + "" + i18nc( " - ","%1 - %2", IncidenceFormatter::dateToString( event->dtStart(), true, spec ), IncidenceFormatter::dateToString( event->dtEnd(), true, spec ) ) + "" + i18n( "Date" ) + "" + i18nc( "date as string","%1", IncidenceFormatter::dateToString( event->dtStart(), true, spec ) ) + "" + i18n( "Time" ) + "" + i18nc( " - ","%1 - %2", IncidenceFormatter::dateToString( event->dtStart(), true, spec ), IncidenceFormatter::dateToString( event->dtEnd(), true, spec ) ) + "" + i18n( "Time" ) + "" + i18nc( " - ","%1 - %2", IncidenceFormatter::timeToString( event->dtStart(), true, spec ), IncidenceFormatter::timeToString( event->dtEnd(), true, spec ) ) + "" + IncidenceFormatter::timeToString( event->dtStart(), true, spec ) + "
    " + i18n( "Date" ) + "" + i18nc( "date as string","%1", IncidenceFormatter::dateToString( event->dtStart(), true, spec ) ) + "
    " + i18n( "Birthday" ) + "" + eventViewerFormatBirthday( event ) + "
    "; return tmpStr; } if ( !event->description().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += "" + eventViewerAddTag( "p", event->richDescription() ) + ""; tmpStr += ""; } if ( event->categories().count() > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += i18np( "1 category", "%1 categories", event->categories().count() ) + ""; tmpStr += "" + event->categoriesStr() + ""; tmpStr += ""; } if ( event->recurs() ) { KDateTime dt = event->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() ); tmpStr += ""; tmpStr += "" + i18n( "Next Occurrence" )+ ""; tmpStr += "" + ( dt.isValid() ? KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) : i18nc( "no date", "none" ) ) + ""; tmpStr += ""; } tmpStr += ""; tmpStr += eventViewerFormatAttendees( event ); tmpStr += ""; int attachmentCount = event->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += ""; tmpStr += ""; tmpStr += i18np( "1 attachment", "%1 attachments", attachmentCount )+ ""; tmpStr += "" + eventViewerFormatAttachments( event ) + ""; tmpStr += ""; } KDateTime kdt = event->created().toTimeSpec( spec ); tmpStr += ""; tmpStr += "

    " + i18n( "Creation date: %1", KGlobal::locale()->formatDateTime( kdt.dateTime(), KLocale::ShortDate ) ) + ""; return tmpStr; } static QString eventViewerFormatTodo( Todo *todo, KDateTime::Spec spec ) { if ( !todo ) { return QString(); } QString tmpStr = eventViewerFormatHeader( todo ); if ( !todo->location().isEmpty() ) { tmpStr += eventViewerAddTag( "b", i18n(" Location: %1", todo->richLocation() ) ); tmpStr += "
    "; } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { tmpStr += i18n( "Due on: %1", IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay(), true, spec ) ); } if ( !todo->description().isEmpty() ) { tmpStr += eventViewerAddTag( "p", todo->richDescription() ); } tmpStr += eventViewerFormatCategories( todo ); if ( todo->priority() > 0 ) { tmpStr += i18n( "

    Priority: %1

    ", todo->priority() ); } else { tmpStr += i18n( "

    Priority: %1

    ", i18n( "Unspecified" ) ); } tmpStr += i18n( "

    %1 % completed

    ", todo->percentComplete() ); if ( todo->recurs() ) { KDateTime dt = todo->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() ); tmpStr += eventViewerAddTag( "p", "" + i18n( "This is a recurring to-do. The next occurrence will be on %1.", KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) ) + "" ); } tmpStr += eventViewerFormatAttendees( todo ); tmpStr += eventViewerFormatAttachments( todo ); KDateTime kdt = todo->created().toTimeSpec( spec ); tmpStr += "

    " + i18n( "Creation date: %1", KGlobal::locale()->formatDateTime( kdt.dateTime(), KLocale::ShortDate ) ) + ""; return tmpStr; } static QString eventViewerFormatJournal( Journal *journal, KDateTime::Spec spec ) { if ( !journal ) { return QString(); } QString tmpStr; if ( !journal->summary().isEmpty() ) { tmpStr+= eventViewerAddTag( "h2", journal->richSummary() ); } tmpStr += eventViewerAddTag( "h3", i18n( "Journal for %1", IncidenceFormatter::dateToString( journal->dtStart(), false, spec ) ) ); if ( !journal->description().isEmpty() ) { tmpStr += eventViewerAddTag( "p", journal->richDescription() ); } return tmpStr; } static QString eventViewerFormatFreeBusy( FreeBusy *fb, KDateTime::Spec spec ) { Q_UNUSED( spec ); if ( !fb ) { return QString(); } QString tmpStr( eventViewerAddTag( "h2", i18n( "Free/Busy information for %1", fb->organizer().fullName() ) ) ); tmpStr += eventViewerAddTag( "h4", i18n( "Busy times in date range %1 - %2:", KGlobal::locale()->formatDate( fb->dtStart().date(), KLocale::ShortDate ), KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) ) ); QList periods = fb->busyPeriods(); QString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) ); QList::iterator it; for ( it = periods.begin(); it != periods.end(); ++it ) { Period per = *it; if ( per.hasDuration() ) { int dur = per.duration().asSeconds(); QString cont; if ( dur >= 3600 ) { cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 ); dur %= 3600; } if ( dur >= 60 ) { cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 ); dur %= 60; } if ( dur > 0 ) { cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur ); } text += i18nc( "startDate for duration", "%1 for %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), cont ); text += "
    "; } else { if ( per.start().date() == per.end().date() ) { text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3", KGlobal::locale()->formatDate( per.start().date() ), KGlobal::locale()->formatTime( per.start().time() ), KGlobal::locale()->formatTime( per.end().time() ) ); } else { text += i18nc( "fromDateTime - toDateTime", "%1 - %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), KGlobal::locale()->formatDateTime( per.end().dateTime(), KLocale::LongDate ) ); } text += "
    "; } } tmpStr += eventViewerAddTag( "p", text ); return tmpStr; } //@endcond //@cond PRIVATE class KCal::IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor { public: EventViewerVisitor() : mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() ) { mSpec = spec; mResult = ""; return incidence->accept( *this ); } QString result() const { return mResult; } protected: bool visit( Event *event ) { mResult = eventViewerFormatEvent( event, mSpec ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = eventViewerFormatTodo( todo, mSpec ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = eventViewerFormatJournal( journal, mSpec ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = eventViewerFormatFreeBusy( fb, mSpec ); return !mResult.isEmpty(); } protected: KDateTime::Spec mSpec; QString mResult; }; //@endcond QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence ) { return extensiveDisplayStr( incidence, KDateTime::Spec() ); } QString IncidenceFormatter::extensiveDisplayStr( IncidenceBase *incidence, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } EventViewerVisitor v; if ( v.act( incidence, spec ) ) { return v.result(); } else { return QString(); } } /******************************************************************* * Helper functions for the body part formatter of kmail *******************************************************************/ //@cond PRIVATE static QString string2HTML( const QString &str ) { return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal ); } static QString cleanHtml( const QString &html ) { QRegExp rx( "]*>(.*)", Qt::CaseInsensitive ); rx.indexIn( html ); QString body = rx.cap( 1 ); return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() ); } static QString eventStartTimeStr( Event *event ) { QString tmp; if ( !event->allDay() ) { tmp = i18nc( "%1: Start Date, %2: Start Time", "%1 %2", IncidenceFormatter::dateToString( event->dtStart(), true, KSystemTimeZones::local() ), IncidenceFormatter::timeToString( event->dtStart(), true, KSystemTimeZones::local() ) ); } else { tmp = i18nc( "%1: Start Date", "%1 (all day)", IncidenceFormatter::dateToString( event->dtStart(), true, KSystemTimeZones::local() ) ); } return tmp; } static QString eventEndTimeStr( Event *event ) { QString tmp; if ( event->hasEndDate() && event->dtEnd().isValid() ) { if ( !event->allDay() ) { tmp = i18nc( "%1: End Date, %2: End Time", "%1 %2", IncidenceFormatter::dateToString( event->dtEnd(), true, KSystemTimeZones::local() ), IncidenceFormatter::timeToString( event->dtEnd(), true, KSystemTimeZones::local() ) ); } else { tmp = i18nc( "%1: End Date", "%1 (all day)", IncidenceFormatter::dateToString( event->dtEnd(), true, KSystemTimeZones::local() ) ); } } return tmp; } static QString invitationRow( const QString &cell1, const QString &cell2 ) { return "" + cell1 + "" + cell2 + "\n"; } static bool iamOrganizer( Incidence *incidence ) { // Check if I'm the organizer for this incidence if ( !incidence ) { return false; } bool iam = false; KEMailSettings settings; QStringList profiles = settings.profiles(); for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) { settings.setProfile( *it ); if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer().email() ) { iam = true; break; } } return iam; } static bool iamAttendee( Attendee *attendee ) { // Check if I'm this attendee bool iam = false; KEMailSettings settings; QStringList profiles = settings.profiles(); for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) { settings.setProfile( *it ); if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) { iam = true; break; } } return iam; } static Attendee *findMyAttendee( Incidence *incidence ) { // Return the attendee for the incidence that is probably me Attendee *attendee = 0; if ( !incidence ) { return attendee; } KEMailSettings settings; QStringList profiles = settings.profiles(); for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) { settings.setProfile( *it ); Attendee::List attendees = incidence->attendees(); Attendee::List::ConstIterator it2; for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) { Attendee *a = *it2; if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) { attendee = a; break; } } } return attendee; } static Attendee *findAttendee( Incidence *incidence, const QString &email ) { // Search for an attendee by email address Attendee *attendee = 0; if ( !incidence ) { return attendee; } Attendee::List attendees = incidence->attendees(); Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; if ( email == a->email() ) { attendee = a; break; } } return attendee; } static bool rsvpRequested( Incidence *incidence ) { if ( !incidence ) { return false; } //use a heuristic to determine if a response is requested. bool rsvp = true; // better send superfluously than not at all Attendee::List attendees = incidence->attendees(); Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { if ( it == attendees.constBegin() ) { rsvp = (*it)->RSVP(); // use what the first one has } else { if ( (*it)->RSVP() != rsvp ) { rsvp = true; // they differ, default break; } } } return rsvp; } static QString rsvpRequestedStr( bool rsvpRequested ) { if ( rsvpRequested ) { return i18n( "Your response is requested" ); } else { return i18n( "A response is not necessary" ); } } static QString invitationPerson( const QString &email, QString name, QString uid ) { // Make the search, if there is an email address to search on, // and either name or uid is missing if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) { KABC::AddressBook *add_book = KABC::StdAddressBook::self( true ); KABC::Addressee::List addressList = add_book->findByEmail( email ); if ( !addressList.isEmpty() ) { KABC::Addressee o = addressList.first(); if ( !o.isEmpty() && addressList.size() < 2 ) { if ( name.isEmpty() ) { // No name set, so use the one from the addressbook name = o.formattedName(); } uid = o.uid(); } else { // Email not found in the addressbook. Don't make a link uid.clear(); } } } // Show the attendee QString tmpString; if ( !uid.isEmpty() ) { // There is a UID, so make a link to the addressbook if ( name.isEmpty() ) { // Use the email address for text tmpString += eventViewerAddLink( "uid:" + uid, email ); } else { tmpString += eventViewerAddLink( "uid:" + uid, name ); } } else { // No UID, just show some text tmpString += ( name.isEmpty() ? email : name ); } tmpString += '\n'; // Make the mailto link if ( !email.isEmpty() ) { KCal::Person person( name, email ); KUrl mailto; mailto.setProtocol( "mailto" ); mailto.setPath( person.fullName() ); const QString iconPath = KIconLoader::global()->iconPath( "mail-message-new", KIconLoader::Small ); tmpString += eventViewerAddLink( mailto.url(), "" ); } tmpString += '\n'; return tmpString; } static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode ) { // if description and comment -> use both // if description, but no comment -> use the desc as the comment (and no desc) // if comment, but no description -> use the comment and no description QString html; QString descr; QStringList comments; if ( incidence->comments().isEmpty() ) { if ( !incidence->description().isEmpty() ) { // use description as comments if ( !incidence->descriptionIsRich() ) { comments << string2HTML( incidence->description() ); } else { comments << incidence->richDescription(); if ( noHtmlMode ) { comments[0] = cleanHtml( comments[0] ); } comments[0] = eventViewerAddTag( "p", comments[0] ); } } //else desc and comments are empty } else { // non-empty comments foreach ( const QString &c, incidence->comments() ) { if ( !c.isEmpty() ) { comments += string2HTML( c ); } } if ( !incidence->description().isEmpty() ) { // use description too if ( !incidence->descriptionIsRich() ) { descr = string2HTML( incidence->description() ); } else { descr = incidence->richDescription(); if ( noHtmlMode ) { descr = cleanHtml( descr ); } descr = eventViewerAddTag( "p", descr ); } } } if( !descr.isEmpty() ) { html += "

    "; html += ""; html += ""; html += ""; html += "
    " + eventViewerAddTag( "u", i18n( "Description:" ) ) + "
    " + descr + "
    "; } if ( !comments.isEmpty() ) { html += "

    "; html += ""; html += ""; html += ""; html += "
    " + eventViewerAddTag( "u", i18n( "Comments:" ) ) + "
    "; if ( comments.count() > 1 ) { html += "
      "; for ( int i=0; i < comments.count(); ++i ) { html += "
    • " + comments[i] + "
    • "; } html += "
    "; } else { html += comments[0]; } html += "
    "; } return html; } static QString invitationDetailsEvent( Event *event, bool noHtmlMode, KDateTime::Spec spec ) { // Invitation details are formatted into an HTML table if ( !event ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); if ( !event->summary().isEmpty() ) { if ( !event->summaryIsRich() ) { sSummary = Qt::escape( event->summary() ); } else { sSummary = event->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } } QString sLocation = i18n( "Location unspecified" ); if ( !event->location().isEmpty() ) { if ( !event->locationIsRich() ) { sLocation = Qt::escape( event->location() ); } else { sLocation = event->richLocation(); if ( noHtmlMode ) { sLocation = cleanHtml( sLocation ); } } } QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" ); QString html = QString( "

    \n" ).arg( dir ); html += ""; // Invitation summary & location rows html += invitationRow( i18n( "What:" ), sSummary ); html += invitationRow( i18n( "Where:" ), sLocation ); // If a 1 day event if ( event->dtStart().date() == event->dtEnd().date() ) { html += invitationRow( i18n( "Date:" ), IncidenceFormatter::dateToString( event->dtStart(), false, spec ) ); if ( !event->allDay() ) { html += invitationRow( i18n( "Time:" ), IncidenceFormatter::timeToString( event->dtStart(), false, spec ) + " - " + IncidenceFormatter::timeToString( event->dtEnd(), false, spec ) ); } } else { html += invitationRow( i18nc( "starting date", "From:" ), IncidenceFormatter::dateToString( event->dtStart(), false, spec ) ); if ( !event->allDay() ) { html += invitationRow( i18nc( "starting time", "At:" ), IncidenceFormatter::timeToString( event->dtStart(), false, spec ) ); } if ( event->hasEndDate() ) { html += invitationRow( i18nc( "ending date", "To:" ), IncidenceFormatter::dateToString( event->dtEnd(), false, spec ) ); if ( !event->allDay() ) { html += invitationRow( i18nc( "ending time", "At:" ), IncidenceFormatter::timeToString( event->dtEnd(), false, spec ) ); } } else { html += invitationRow( i18nc( "ending date", "To:" ), i18n( "no end date specified" ) ); } } // Invitation Duration Row if ( !event->allDay() && event->hasEndDate() && event->dtEnd().isValid() ) { QString tmp; QTime sDuration( 0, 0, 0 ), t; int secs = event->dtStart().secsTo( event->dtEnd() ); t = sDuration.addSecs( secs ); if ( t.hour() > 0 ) { tmp += i18np( "1 hour ", "%1 hours ", t.hour() ); } if ( t.minute() > 0 ) { tmp += i18np( "1 minute ", "%1 minutes ", t.minute() ); } html += invitationRow( i18n( "Duration:" ), tmp ); } if ( event->recurs() ) { html += invitationRow( i18n( "Recurrence:" ), IncidenceFormatter::recurrenceString( event ) ); } html += "
    \n"; html += invitationsDetailsIncidence( event, noHtmlMode ); return html; } static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode, KDateTime::Spec spec ) { // To-do details are formatted into an HTML table if ( !todo ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); if ( !todo->summary().isEmpty() ) { if ( !todo->summaryIsRich() ) { sSummary = Qt::escape( todo->summary() ); } else { sSummary = todo->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } } QString sLocation = i18n( "Location unspecified" ); if ( !todo->location().isEmpty() ) { if ( !todo->locationIsRich() ) { sLocation = Qt::escape( todo->location() ); } else { sLocation = todo->richLocation(); if ( noHtmlMode ) { sLocation = cleanHtml( sLocation ); } } } QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" ); QString html = QString( "
    \n" ).arg( dir ); html += ""; // Invitation summary & location rows html += invitationRow( i18n( "What:" ), sSummary ); html += invitationRow( i18n( "Where:" ), sLocation ); if ( todo->hasStartDate() && todo->dtStart().isValid() ) { html += invitationRow( i18n( "Start Date:" ), IncidenceFormatter::dateToString( todo->dtStart(), false, spec ) ); } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { html += invitationRow( i18n( "Due Date:" ), IncidenceFormatter::dateToString( todo->dtDue(), false, spec ) ); } else { html += invitationRow( i18n( "Due Date:" ), i18nc( "no to-do due date", "None" ) ); } html += "
    \n"; html += invitationsDetailsIncidence( todo, noHtmlMode ); return html; } static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode, KDateTime::Spec spec ) { if ( !journal ) { return QString(); } QString sSummary = i18n( "Summary unspecified" ); QString sDescr = i18n( "Description unspecified" ); if ( ! journal->summary().isEmpty() ) { sSummary = journal->richSummary(); if ( noHtmlMode ) { sSummary = cleanHtml( sSummary ); } } if ( ! journal->description().isEmpty() ) { sDescr = journal->richDescription(); if ( noHtmlMode ) { sDescr = cleanHtml( sDescr ); } } QString html( "\n" ); html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Date:" ), IncidenceFormatter::dateToString( journal->dtStart(), false, spec ) ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
    \n"; html += invitationsDetailsIncidence( journal, noHtmlMode ); return html; } static QString invitationDetailsFreeBusy( FreeBusy *fb, bool noHtmlMode, KDateTime::Spec spec ) { Q_UNUSED( noHtmlMode ); if ( !fb ) { return QString(); } QString html( "\n" ); html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() ); html += invitationRow( i18n( "Start date:" ), IncidenceFormatter::dateToString( fb->dtStart(), true, spec ) ); html += invitationRow( i18n( "End date:" ), IncidenceFormatter::dateToString( fb->dtEnd(), true, spec ) ); html += "\n"; html += "\n"; QList periods = fb->busyPeriods(); QList::iterator it; for ( it = periods.begin(); it != periods.end(); ++it ) { Period per = *it; if ( per.hasDuration() ) { int dur = per.duration().asSeconds(); QString cont; if ( dur >= 3600 ) { cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 ); dur %= 3600; } if ( dur >= 60 ) { cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 ); dur %= 60; } if ( dur > 0 ) { cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur ); } html += invitationRow( QString(), i18nc( "startDate for duration", "%1 for %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), cont ) ); } else { QString cont; if ( per.start().date() == per.end().date() ) { cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3", KGlobal::locale()->formatDate( per.start().date() ), KGlobal::locale()->formatTime( per.start().time() ), KGlobal::locale()->formatTime( per.end().time() ) ); } else { cont = i18nc( "fromDateTime - toDateTime", "%1 - %2", KGlobal::locale()->formatDateTime( per.start().dateTime(), KLocale::LongDate ), KGlobal::locale()->formatDateTime( per.end().dateTime(), KLocale::LongDate ) ); } html += invitationRow( QString(), cont ); } } html += "

    Busy periods given in this free/busy object:
    \n"; return html; } static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg ) { if ( !msg || !event ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This event has been published" ); case iTIPRequest: if ( event->revision() > 0 ) { return i18n( "This invitation has been updated" ); } if ( iamOrganizer( event ) ) { return i18n( "I sent this invitation" ); } else { if ( !event->organizer().fullName().isEmpty() ) { return i18n( "You received an invitation from %1", event->organizer().fullName() ); } else { return i18n( "You received an invitation" ); } } case iTIPRefresh: return i18n( "This invitation was refreshed" ); case iTIPCancel: return i18n( "This invitation has been canceled" ); case iTIPAdd: return i18n( "Addition to the invitation" ); case iTIPReply: { Attendee::List attendees = event->attendees(); if( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if ( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1" << "but is" << attendees.count(); } Attendee *attendee = *attendees.begin(); QString attendeeName = attendee->name(); if ( attendeeName.isEmpty() ) { attendeeName = attendee->email(); } if ( attendeeName.isEmpty() ) { attendeeName = i18n( "Sender" ); } QString delegatorName, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName ); if ( delegatorName.isEmpty() ) { delegatorName = attendee->delegator(); } switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "%1 indicates this invitation still needs some action", attendeeName ); case Attendee::Accepted: if ( delegatorName.isEmpty() ) { return i18n( "%1 accepts this invitation", attendeeName ); } else { return i18n( "%1 accepts this invitation on behalf of %2", attendeeName, delegatorName ); } case Attendee::Tentative: if ( delegatorName.isEmpty() ) { return i18n( "%1 tentatively accepts this invitation", attendeeName ); } else { return i18n( "%1 tentatively accepts this invitation on behalf of %2", attendeeName, delegatorName ); } case Attendee::Declined: if ( delegatorName.isEmpty() ) { return i18n( "%1 declines this invitation", attendeeName ); } else { return i18n( "%1 declines this invitation on behalf of %2", attendeeName, delegatorName ); } case Attendee::Delegated: { QString delegate, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); if ( delegate.isEmpty() ) { delegate = attendee->delegate(); } if ( !delegate.isEmpty() ) { return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate ); } else { return i18n( "%1 has delegated this invitation", attendeeName ); } } case Attendee::Completed: return i18n( "This invitation is now completed" ); case Attendee::InProcess: return i18n( "%1 is still processing the invitation", attendeeName ); default: return i18n( "Unknown response to this invitation" ); } break; } case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() ); } return QString(); } static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg ) { if ( !msg || !todo ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This to-do has been published" ); case iTIPRequest: if ( todo->revision() > 0 ) { return i18n( "This to-do has been updated" ); } else { return i18n( "You have been assigned this to-do" ); } case iTIPRefresh: return i18n( "This to-do was refreshed" ); case iTIPCancel: return i18n( "This to-do was canceled" ); case iTIPAdd: return i18n( "Addition to the to-do" ); case iTIPReply: { Attendee::List attendees = todo->attendees(); if ( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if ( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1" << "but is" << attendees.count(); } Attendee *attendee = *attendees.begin(); switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "Sender indicates this to-do assignment still needs some action" ); case Attendee::Accepted: return i18n( "Sender accepts this to-do" ); case Attendee::Tentative: return i18n( "Sender tentatively accepts this to-do" ); case Attendee::Declined: return i18n( "Sender declines this to-do" ); case Attendee::Delegated: { QString delegate, dummy; KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); if ( delegate.isEmpty() ) { delegate = attendee->delegate(); } if ( !delegate.isEmpty() ) { return i18n( "Sender has delegated this request for the to-do to %1", delegate ); } else { return i18n( "Sender has delegated this request for the to-do " ); } } case Attendee::Completed: return i18n( "The request for this to-do is now completed" ); case Attendee::InProcess: return i18n( "Sender is still processing the invitation" ); default: return i18n( "Unknown response to this to-do" ); } break; } case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() ); } return QString(); } static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg ) { // TODO: Several of the methods are not allowed for journals, so remove them. if ( !msg || !journal ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This journal has been published" ); case iTIPRequest: return i18n( "You have been assigned this journal" ); case iTIPRefresh: return i18n( "This journal was refreshed" ); case iTIPCancel: return i18n( "This journal was canceled" ); case iTIPAdd: return i18n( "Addition to the journal" ); case iTIPReply: { Attendee::List attendees = journal->attendees(); if ( attendees.count() == 0 ) { kDebug() << "No attendees in the iCal reply!"; return QString(); } if( attendees.count() != 1 ) { kDebug() << "Warning: attendeecount in the reply should be 1 " << "but is " << attendees.count(); } Attendee *attendee = *attendees.begin(); switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "Sender indicates this journal assignment still needs some action" ); case Attendee::Accepted: return i18n( "Sender accepts this journal" ); case Attendee::Tentative: return i18n( "Sender tentatively accepts this journal" ); case Attendee::Declined: return i18n( "Sender declines this journal" ); case Attendee::Delegated: return i18n( "Sender has delegated this request for the journal" ); case Attendee::Completed: return i18n( "The request for this journal is now completed" ); case Attendee::InProcess: return i18n( "Sender is still processing the invitation" ); default: return i18n( "Unknown response to this journal" ); } break; } case iTIPCounter: return i18n( "Sender makes this counter proposal" ); case iTIPDeclineCounter: return i18n( "Sender declines the counter proposal" ); case iTIPNoMethod: return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() ); } return QString(); } static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg ) { if ( !msg || !fb ) { return QString(); } switch ( msg->method() ) { case iTIPPublish: return i18n( "This free/busy list has been published" ); case iTIPRequest: return i18n( "The free/busy list has been requested" ); case iTIPRefresh: return i18n( "This free/busy list was refreshed" ); case iTIPCancel: return i18n( "This free/busy list was canceled" ); case iTIPAdd: return i18n( "Addition to the free/busy list" ); case iTIPNoMethod: default: return i18n( "Error: Free/Busy iMIP message with unknown method: '%1'", msg->method() ); } } //@endcond static QString invitationAttendees( Incidence *incidence ) { QString tmpStr; if ( !incidence ) { return tmpStr; } tmpStr += i18n( "Invitation List" ); int count=0; Attendee::List attendees = incidence->attendees(); if ( !attendees.isEmpty() ) { Attendee::List::ConstIterator it; for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { Attendee *a = *it; if ( !iamAttendee( a ) ) { count++; if ( count == 1 ) { tmpStr += ""; } tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } } } if ( count ) { tmpStr += "
    "; tmpStr += invitationPerson( a->email(), a->name(), QString() ); if ( !a->delegator().isEmpty() ) { tmpStr += i18n( " (delegated by %1)", a->delegator() ); } if ( !a->delegate().isEmpty() ) { tmpStr += i18n( " (delegated to %1)", a->delegate() ); } tmpStr += "" + a->statusStr() + "
    "; } else { tmpStr += "" + i18nc( "no attendees", "None" ) + ""; } return tmpStr; } +static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence ) +{ + QString tmpStr; + if ( !incidence ) { + return tmpStr; + } + + tmpStr += "" + i18n( "Attached documents" ) + ""; + tmpStr += "
    "; + + Attachment::List attachments = incidence->attachments(); + if ( !attachments.isEmpty() ) { + tmpStr += i18n( "Attached Documents:" ) + "
      "; + + Attachment::List::ConstIterator it; + for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) { + Attachment *a = *it; + tmpStr += "
    1. "; + // Attachment icon + KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() ); + QString iconStr = mimeType->iconName( a->uri() ); + QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small ); + if ( !iconPath.isEmpty() ) { + tmpStr += ""; + } + tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() ); + tmpStr += "
    2. "; + } + tmpStr += "
    "; + } + + return tmpStr; +} + //@cond PRIVATE class KCal::IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor { public: ScheduleMessageVisitor() : mMessage(0) { mResult = ""; } bool act( IncidenceBase *incidence, ScheduleMessage *msg ) { mMessage = msg; return incidence->accept( *this ); } QString result() const { return mResult; } protected: QString mResult; ScheduleMessage *mMessage; }; class KCal::IncidenceFormatter::InvitationHeaderVisitor : public IncidenceFormatter::ScheduleMessageVisitor { protected: bool visit( Event *event ) { mResult = invitationHeaderEvent( event, mMessage ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = invitationHeaderTodo( todo, mMessage ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = invitationHeaderJournal( journal, mMessage ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = invitationHeaderFreeBusy( fb, mMessage ); return !mResult.isEmpty(); } }; class KCal::IncidenceFormatter::InvitationBodyVisitor : public IncidenceFormatter::ScheduleMessageVisitor { public: InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec ) : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {} protected: bool visit( Event *event ) { mResult = invitationDetailsEvent( event, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = invitationDetailsTodo( todo, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = invitationDetailsJournal( journal, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode, mSpec ); return !mResult.isEmpty(); } private: bool mNoHtmlMode; KDateTime::Spec mSpec; }; //@endcond QString InvitationFormatterHelper::generateLinkURL( const QString &id ) { return id; } //@cond PRIVATE class IncidenceFormatter::IncidenceCompareVisitor : public IncidenceBase::Visitor { public: IncidenceCompareVisitor() : mExistingIncidence( 0 ) {} bool act( IncidenceBase *incidence, Incidence *existingIncidence ) { if ( !existingIncidence ) { return false; } Incidence *inc = dynamic_cast( incidence ); if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() ) { return false; } mExistingIncidence = existingIncidence; return incidence->accept( *this ); } QString result() const { if ( mChanges.isEmpty() ) { return QString(); } QString html = "
    • "; html += mChanges.join( "
    • " ); html += "
      "; return html; } protected: bool visit( Event *event ) { compareEvents( event, dynamic_cast( mExistingIncidence ) ); compareIncidences( event, mExistingIncidence ); return !mChanges.isEmpty(); } bool visit( Todo *todo ) { compareIncidences( todo, mExistingIncidence ); return !mChanges.isEmpty(); } bool visit( Journal *journal ) { compareIncidences( journal, mExistingIncidence ); return !mChanges.isEmpty(); } bool visit( FreeBusy *fb ) { Q_UNUSED( fb ); return !mChanges.isEmpty(); } private: void compareEvents( Event *newEvent, Event *oldEvent ) { if ( !oldEvent || !newEvent ) { return; } if ( oldEvent->dtStart() != newEvent->dtStart() || oldEvent->allDay() != newEvent->allDay() ) { mChanges += i18n( "The invitation starting time has been changed from %1 to %2", eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) ); } if ( oldEvent->dtEnd() != newEvent->dtEnd() || oldEvent->allDay() != newEvent->allDay() ) { mChanges += i18n( "The invitation ending time has been changed from %1 to %2", eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) ); } } void compareIncidences( Incidence *newInc, Incidence *oldInc ) { if ( !oldInc || !newInc ) { return; } if ( oldInc->summary() != newInc->summary() ) { mChanges += i18n( "The summary has been changed to: \"%1\"", newInc->richSummary() ); } if ( oldInc->location() != newInc->location() ) { mChanges += i18n( "The location has been changed to: \"%1\"", newInc->richLocation() ); } if ( oldInc->description() != newInc->description() ) { mChanges += i18n( "The description has been changed to: \"%1\"", newInc->richDescription() ); } Attendee::List oldAttendees = oldInc->attendees(); Attendee::List newAttendees = newInc->attendees(); for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); it != newAttendees.constEnd(); ++it ) { Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() ); if ( !oldAtt ) { mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() ); } else { if ( oldAtt->status() != (*it)->status() ) { mChanges += i18n( "The status of attendee %1 has been changed to: %2", (*it)->fullName(), (*it)->statusStr() ); } } } for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); it != oldAttendees.constEnd(); ++it ) { Attendee *newAtt = newInc->attendeeByMail( (*it)->email() ); if ( !newAtt ) { mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() ); } } } private: Incidence *mExistingIncidence; QStringList mChanges; }; //@endcond QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text ) { - QString res( "%2" ); - return res.arg( generateLinkURL( id ) ).arg( text ); - return res; + if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) { + QString res( "%2" ); + return res.arg( generateLinkURL( id ) ).arg( text ); + } else { + // draw the attachment links in non-bold face + QString res( "%2" ); + return res.arg( generateLinkURL( id ) ).arg( text ); + } } // Check if the given incidence is likely one that we own instead one from // a shared calendar (Kolab-specific) static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence ) { CalendarResources *cal = dynamic_cast( calendar ); if ( !cal || !incidence ) { return true; } ResourceCalendar *res = cal->resource( incidence ); if ( !res ) { return true; } const QString subRes = res->subresourceIdentifier( incidence ); if ( !subRes.contains( "/.INBOX.directory/" ) ) { return false; } return true; } Calendar *InvitationFormatterHelper::calendar() const { return 0; } static QString formatICalInvitationHelper( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper, bool noHtmlMode, KDateTime::Spec spec ) { if ( invitation.isEmpty() ) { return QString(); } ICalFormat format; // parseScheduleMessage takes the tz from the calendar, // no need to set it manually here for the format! ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation ); if( !msg ) { kDebug() << "Failed to parse the scheduling message"; Q_ASSERT( format.exception() ); kDebug() << format.exception()->message(); return QString(); } IncidenceBase *incBase = msg->event(); incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() ); // Determine if this incidence is in my calendar (and owned by me) Incidence *existingIncidence = 0; if ( incBase && helper->calendar() ) { existingIncidence = helper->calendar()->incidence( incBase->uid() ); if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) { existingIncidence = 0; } if ( !existingIncidence ) { const Incidence::List list = helper->calendar()->incidences(); for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) { if ( (*it)->schedulingID() == incBase->uid() && incidenceOwnedByMe( helper->calendar(), *it ) ) { existingIncidence = *it; break; } } } } // First make the text of the message QString html; html += "
      "; IncidenceFormatter::InvitationHeaderVisitor headerVisitor; // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled if ( !headerVisitor.act( incBase, msg ) ) { return QString(); } html += eventViewerAddTag( "h3", headerVisitor.result() ); IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec ); if ( !bodyVisitor.act( incBase, msg ) ) { return QString(); } html += bodyVisitor.result(); if ( msg->method() == iTIPRequest ) { // ### Scheduler::Publish/Refresh/Add as well? IncidenceFormatter::IncidenceCompareVisitor compareVisitor; if ( compareVisitor.act( incBase, existingIncidence ) ) { html += i18n( "

      The following changes have been made by the organizer:

      " ); html += compareVisitor.result(); } } Incidence *inc = dynamic_cast( incBase ); // determine if I am the organizer for this invitation bool myInc = iamOrganizer( inc ); // determine if the invitation response has already been recorded bool rsvpRec = false; Attendee *ea = 0; if ( !myInc ) { if ( existingIncidence ) { ea = findMyAttendee( existingIncidence ); } if ( ea && ( ea->status() == Attendee::Accepted || ea->status() == Attendee::Declined ) ) { rsvpRec = true; } } // Print if RSVP needed, not-needed, or response already recorded bool rsvpReq = rsvpRequested( inc ); if ( !myInc ) { html += "
      "; html += ""; if ( rsvpRec && ( inc && inc->revision() == 0 ) ) { html += i18n( "Your response has already been recorded [%1]", ea->statusStr() ); rsvpReq = false; } else if ( msg->method() == iTIPCancel ) { html += i18n( "This invitation was declined" ); } else if ( msg->method() == iTIPAdd ) { html += i18n( "This invitation was accepted" ); } else { html += rsvpRequestedStr( rsvpReq ); } html += "
      "; } // Add groupware links html += "

      "; html += ""; const QString tdOpen = ""; switch ( msg->method() ) { case iTIPPublish: case iTIPRequest: case iTIPRefresh: case iTIPAdd: { if ( inc && inc->revision() > 0 && existingIncidence ) { if ( inc->type() == "Todo" ) { html += helper->makeLink( "reply", i18n( "[Record invitation into my to-do list]" ) ); } else { html += helper->makeLink( "reply", i18n( "[Record invitation into my calendar]" ) ); } } if ( !myInc ) { if ( rsvpReq ) { // Accept html += tdOpen; html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) ); html += tdClose; // Accept conditionally html += tdOpen; html += helper->makeLink( "accept_conditionally", i18nc( "Accept invitation conditionally", "Accept cond." ) ); html += tdClose; } if ( rsvpReq ) { // Counter proposal html += tdOpen; html += helper->makeLink( "counter", i18nc( "invitation counter proposal", "Counter proposal" ) ); html += tdClose; } if ( rsvpReq ) { // Decline html += tdOpen; html += helper->makeLink( "decline", i18nc( "decline invitation", "Decline" ) ); html += tdClose; } if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) { // Delegate html += tdOpen; html += helper->makeLink( "delegate", i18nc( "delegate inviation to another", "Delegate" ) ); html += tdClose; // Forward html += tdOpen; html += helper->makeLink( "forward", i18nc( "forward request to another", "Forward" ) ); html += tdClose; // Check calendar if ( incBase && incBase->type() == "Event" ) { html += tdOpen; html += helper->makeLink( "check_calendar", i18nc( "look for scheduling conflicts", "Check my calendar" ) ); html += tdClose; } } } break; } case iTIPCancel: // Remove invitation html += tdOpen; if ( inc->type() == "Todo" ) { html += helper->makeLink( "cancel", i18n( "Remove invitation from my to-do list" ) ); } else { html += helper->makeLink( "cancel", i18n( "Remove invitation from my calendar" ) ); } html += tdClose; break; case iTIPReply: { // Record invitation response Attendee *a = 0; Attendee *ea = 0; if ( inc ) { a = inc->attendees().first(); if ( a && helper->calendar() ) { ea = findAttendee( existingIncidence, a->email() ); } } if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) { html += tdOpen; html += eventViewerAddTag( "i", i18n( "The response has already been recorded" ) ); html += tdClose; } else { if ( inc ) { if ( inc->type() == "Todo" ) { html += helper->makeLink( "reply", i18n( "[Record response into my to-do list]" ) ); } else { html += helper->makeLink( "reply", i18n( "[Record response into my calendar]" ) ); } } } break; } case iTIPCounter: // Counter proposal html += tdOpen; html += helper->makeLink( "accept_counter", i18n( "Accept" ) ); html += tdClose; html += tdOpen; html += helper->makeLink( "decline_counter", i18n( "Decline" ) ); html += tdClose; html += tdOpen; html += helper->makeLink( "check_calendar", i18n( "Check my calendar" ) ); html += tdClose; break; case iTIPDeclineCounter: case iTIPNoMethod: break; } // close the groupware table html += "
      "; const QString tdClose = "
      "; // Add the attendee list if I am the organizer if ( myInc && helper->calendar() ) { html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) ); } // close the top-level html += "

      "; + + // Add the attachment list + html += invitationAttachments( helper, inc ); + return html; } //@endcond QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper ) { return formatICalInvitationHelper( invitation, mCalendar, helper, false, KSystemTimeZones::local() ); } QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper ) { return formatICalInvitationHelper( invitation, mCalendar, helper, true, KSystemTimeZones::local() ); } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ //@cond PRIVATE class KCal::IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor { public: ToolTipVisitor() : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( IncidenceBase *incidence, bool richText=true, KDateTime::Spec spec=KDateTime::Spec() ) { mRichText = richText; mSpec = spec; mResult = ""; return incidence ? incidence->accept( *this ) : false; } QString result() const { return mResult; } protected: bool visit( Event *event ); bool visit( Todo *todo ); bool visit( Journal *journal ); bool visit( FreeBusy *fb ); QString dateRangeText( Event *event ); QString dateRangeText( Todo *todo ); QString dateRangeText( Journal *journal ); QString dateRangeText( FreeBusy *fb ); QString generateToolTip( Incidence *incidence, QString dtRangeText ); protected: bool mRichText; KDateTime::Spec mSpec; QString mResult; }; QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event ) { //FIXME: support mRichText==false QString ret; QString tmp; if ( event->isMultiDay() ) { tmp = IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ); ret += "
      " + i18nc( "Event start", "From: %1", tmp ); tmp = IncidenceFormatter::dateToString( event->dtEnd(), true, mSpec ); ret += "
      " + i18nc( "Event end","To: %1", tmp ); } else { ret += "
      " + i18n( "Date: %1", IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ) ); if ( !event->allDay() ) { const QString dtStartTime = IncidenceFormatter::timeToString( event->dtStart(), true, mSpec ); const QString dtEndTime = IncidenceFormatter::timeToString( event->dtEnd(), true, mSpec ); if ( dtStartTime == dtEndTime ) { // to prevent 'Time: 17:00 - 17:00' tmp = "
      " + i18nc( "time for event", "Time: %1", dtStartTime ); } else { tmp = "
      " + i18nc( "time range for event", "Time: %1 - %2", dtStartTime, dtEndTime ); } ret += tmp; } } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo ) { //FIXME: support mRichText==false QString ret; if ( todo->hasStartDate() && todo->dtStart().isValid() ) { // No need to add here. This is separated issue and each line // is very visible on its own. On the other hand... Yes, I like it // italics here :) ret += "
      " + i18n( "Start: %1", IncidenceFormatter::dateToString( todo->dtStart( false ), true, mSpec ) ); } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { ret += "
      " + i18n( "Due: %1", IncidenceFormatter::dateTimeToString( todo->dtDue(), todo->allDay(), true, mSpec ) ); } if ( todo->isCompleted() ) { ret += "
      " + i18n( "Completed: %1", todo->completedStr() ); } else { ret += "
      " + i18nc( "percent complete", "%1 % completed", todo->percentComplete() ); } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal ) { //FIXME: support mRichText==false QString ret; if ( journal->dtStart().isValid() ) { ret += "
      " + i18n( "Date: %1", IncidenceFormatter::dateToString( journal->dtStart(), false, mSpec ) ); } return ret.replace( ' ', " " ); } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb ) { //FIXME: support mRichText==false QString ret; ret = "
      " + i18n( "Period start: %1", KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) ); ret += "
      " + i18n( "Period start: %1", KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) ); return ret.replace( ' ', " " ); } bool IncidenceFormatter::ToolTipVisitor::visit( Event *event ) { mResult = generateToolTip( event, dateRangeText( event ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo ) { mResult = generateToolTip( todo, dateRangeText( todo ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal ) { mResult = generateToolTip( journal, dateRangeText( journal ) ); return !mResult.isEmpty(); } bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb ) { //FIXME: support mRichText==false mResult = "" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + ""; mResult += dateRangeText( fb ); mResult += ""; return !mResult.isEmpty(); } QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence, QString dtRangeText ) { //FIXME: support mRichText==false if ( !incidence ) { return QString(); } QString tmp = ""+ incidence->richSummary() + ""; tmp += dtRangeText; if ( !incidence->location().isEmpty() ) { // Put Location: in italics tmp += "
      " + i18n( "Location: %1", incidence->richLocation() ); } if ( !incidence->description().isEmpty() ) { QString desc( incidence->description() ); if ( !incidence->descriptionIsRich() ) { if ( desc.length() > 120 ) { desc = desc.left( 120 ) + "..."; } desc = Qt::escape( desc ).replace( '\n', "
      " ); } else { // TODO: truncate the description when it's rich text } tmp += "
      ----------
      " + i18n( "Description:" ) + "
      " + desc; } tmp += "
      "; return tmp; } //@endcond QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText ) { return toolTipStr( incidence, richText, KDateTime::Spec() ); } QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence, bool richText, KDateTime::Spec spec ) { ToolTipVisitor v; if ( v.act( incidence, richText, spec ) ) { return v.result(); } else { return QString(); } } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ //@cond PRIVATE static QString mailBodyIncidence( Incidence *incidence ) { QString body; if ( !incidence->summary().isEmpty() ) { body += i18n( "Summary: %1\n", incidence->richSummary() ); } if ( !incidence->organizer().isEmpty() ) { body += i18n( "Organizer: %1\n", incidence->organizer().fullName() ); } if ( !incidence->location().isEmpty() ) { body += i18n( "Location: %1\n", incidence->richLocation() ); } return body; } //@endcond //@cond PRIVATE class KCal::IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor { public: MailBodyVisitor() : mSpec( KDateTime::Spec() ), mResult( "" ) {} bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() ) { mSpec = spec; mResult = ""; return incidence ? incidence->accept( *this ) : false; } QString result() const { return mResult; } protected: bool visit( Event *event ); bool visit( Todo *todo ); bool visit( Journal *journal ); bool visit( FreeBusy * ) { mResult = i18n( "This is a Free Busy Object" ); return !mResult.isEmpty(); } protected: KDateTime::Spec mSpec; QString mResult; }; bool IncidenceFormatter::MailBodyVisitor::visit( Event *event ) { QString recurrence[]= { i18nc( "no recurrence", "None" ), i18nc( "event recurs by minutes", "Minutely" ), i18nc( "event recurs by hours", "Hourly" ), i18nc( "event recurs by days", "Daily" ), i18nc( "event recurs by weeks", "Weekly" ), i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ), i18nc( "event recurs same day each month", "Monthly Same Day" ), i18nc( "event recurs same month each year", "Yearly Same Month" ), i18nc( "event recurs same day each year", "Yearly Same Day" ), i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" ) }; mResult = mailBodyIncidence( event ); mResult += i18n( "Start Date: %1\n", IncidenceFormatter::dateToString( event->dtStart(), true, mSpec ) ); if ( !event->allDay() ) { mResult += i18n( "Start Time: %1\n", IncidenceFormatter::timeToString( event->dtStart(), true, mSpec ) ); } if ( event->dtStart() != event->dtEnd() ) { mResult += i18n( "End Date: %1\n", IncidenceFormatter::dateToString( event->dtEnd(), true, mSpec ) ); } if ( !event->allDay() ) { mResult += i18n( "End Time: %1\n", IncidenceFormatter::timeToString( event->dtEnd(), true, mSpec ) ); } if ( event->recurs() ) { Recurrence *recur = event->recurrence(); // TODO: Merge these two to one of the form "Recurs every 3 days" mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] ); mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() ); if ( recur->duration() > 0 ) { mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() ); mResult += '\n'; } else { if ( recur->duration() != -1 ) { // TODO_Recurrence: What to do with all-day QString endstr; if ( event->allDay() ) { endstr = KGlobal::locale()->formatDate( recur->endDate() ); } else { endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() ); } mResult += i18n( "Repeat until: %1\n", endstr ); } else { mResult += i18n( "Repeats forever\n" ); } } } QString details = event->richDescription(); if ( !details.isEmpty() ) { mResult += i18n( "Details:\n%1\n", details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo ) { mResult = mailBodyIncidence( todo ); if ( todo->hasStartDate() && todo->dtStart().isValid() ) { mResult += i18n( "Start Date: %1\n", IncidenceFormatter::dateToString( todo->dtStart(false), true, mSpec ) ); if ( !todo->allDay() ) { mResult += i18n( "Start Time: %1\n", IncidenceFormatter::timeToString( todo->dtStart(false), true, mSpec ) ); } } if ( todo->hasDueDate() && todo->dtDue().isValid() ) { mResult += i18n( "Due Date: %1\n", IncidenceFormatter::dateToString( todo->dtDue(), true, mSpec ) ); if ( !todo->allDay() ) { mResult += i18n( "Due Time: %1\n", IncidenceFormatter::timeToString( todo->dtDue(), true, mSpec ) ); } } QString details = todo->richDescription(); if ( !details.isEmpty() ) { mResult += i18n( "Details:\n%1\n", details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal ) { mResult = mailBodyIncidence( journal ); mResult += i18n( "Date: %1\n", IncidenceFormatter::dateToString( journal->dtStart(), true, mSpec ) ); if ( !journal->allDay() ) { mResult += i18n( "Time: %1\n", IncidenceFormatter::timeToString( journal->dtStart(), true, mSpec ) ); } if ( !journal->description().isEmpty() ) { mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() ); } return !mResult.isEmpty(); } //@endcond QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence ) { return mailBodyStr( incidence, KDateTime::Spec() ); } QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence, KDateTime::Spec spec ) { if ( !incidence ) { return QString(); } MailBodyVisitor v; if ( v.act( incidence, spec ) ) { return v.result(); } return QString(); } //@cond PRIVATE static QString recurEnd( Incidence *incidence ) { QString endstr; if ( incidence->allDay() ) { endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() ); } else { endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() ); } return endstr; } //@endcond QString IncidenceFormatter::recurrenceString( Incidence *incidence ) { if ( !incidence->recurs() ) { return i18n( "No recurrence" ); } QStringList dayList; dayList.append( i18n( "31st Last" ) ); dayList.append( i18n( "30th Last" ) ); dayList.append( i18n( "29th Last" ) ); dayList.append( i18n( "28th Last" ) ); dayList.append( i18n( "27th Last" ) ); dayList.append( i18n( "26th Last" ) ); dayList.append( i18n( "25th Last" ) ); dayList.append( i18n( "24th Last" ) ); dayList.append( i18n( "23rd Last" ) ); dayList.append( i18n( "22nd Last" ) ); dayList.append( i18n( "21st Last" ) ); dayList.append( i18n( "20th Last" ) ); dayList.append( i18n( "19th Last" ) ); dayList.append( i18n( "18th Last" ) ); dayList.append( i18n( "17th Last" ) ); dayList.append( i18n( "16th Last" ) ); dayList.append( i18n( "15th Last" ) ); dayList.append( i18n( "14th Last" ) ); dayList.append( i18n( "13th Last" ) ); dayList.append( i18n( "12th Last" ) ); dayList.append( i18n( "11th Last" ) ); dayList.append( i18n( "10th Last" ) ); dayList.append( i18n( "9th Last" ) ); dayList.append( i18n( "8th Last" ) ); dayList.append( i18n( "7th Last" ) ); dayList.append( i18n( "6th Last" ) ); dayList.append( i18n( "5th Last" ) ); dayList.append( i18n( "4th Last" ) ); dayList.append( i18n( "3rd Last" ) ); dayList.append( i18n( "2nd Last" ) ); dayList.append( i18nc( "last day of the month", "Last" ) ); dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI dayList.append( i18n( "1st" ) ); dayList.append( i18n( "2nd" ) ); dayList.append( i18n( "3rd" ) ); dayList.append( i18n( "4th" ) ); dayList.append( i18n( "5th" ) ); dayList.append( i18n( "6th" ) ); dayList.append( i18n( "7th" ) ); dayList.append( i18n( "8th" ) ); dayList.append( i18n( "9th" ) ); dayList.append( i18n( "10th" ) ); dayList.append( i18n( "11th" ) ); dayList.append( i18n( "12th" ) ); dayList.append( i18n( "13th" ) ); dayList.append( i18n( "14th" ) ); dayList.append( i18n( "15th" ) ); dayList.append( i18n( "16th" ) ); dayList.append( i18n( "17th" ) ); dayList.append( i18n( "18th" ) ); dayList.append( i18n( "19th" ) ); dayList.append( i18n( "20th" ) ); dayList.append( i18n( "21st" ) ); dayList.append( i18n( "22nd" ) ); dayList.append( i18n( "23rd" ) ); dayList.append( i18n( "24th" ) ); dayList.append( i18n( "25th" ) ); dayList.append( i18n( "26th" ) ); dayList.append( i18n( "27th" ) ); dayList.append( i18n( "28th" ) ); dayList.append( i18n( "29th" ) ); dayList.append( i18n( "30th" ) ); dayList.append( i18n( "31st" ) ); int weekStart = KGlobal::locale()->weekStartDay(); QString dayNames; QString txt; const KCalendarSystem *calSys = KGlobal::locale()->calendar(); Recurrence *recur = incidence->recurrence(); switch ( recur->recurrenceType() ) { case Recurrence::rNone: return i18n( "No recurrence" ); case Recurrence::rMinutely: if ( recur->duration() != -1 ) { txt = i18np( "Recurs every minute until %2", "Recurs every %1 minutes until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs every minute", "Recurs every %1 minutes", recur->frequency() ); case Recurrence::rHourly: if ( recur->duration() != -1 ) { txt = i18np( "Recurs hourly until %2", "Recurs every %1 hours until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() ); case Recurrence::rDaily: if ( recur->duration() != -1 ) { txt = i18np( "Recurs daily until %2", "Recurs every %1 days until %2", recur->frequency(), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() ); case Recurrence::rWeekly: { bool addSpace = false; for ( int i = 0; i < 7; ++i ) { if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) { if ( addSpace ) { dayNames.append( i18nc( "separator for list of days", ", " ) ); } dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, KCalendarSystem::ShortDayName ) ); addSpace = true; } } if ( dayNames.isEmpty() ) { dayNames = i18nc( "Recurs weekly on no days", "no days" ); } if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs weekly on [list of days] until end-date", "Recurs weekly on %2 until %3", "Recurs every %1 weeks on %2 until %3", recur->frequency(), dayNames, recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs weekly on [list of days]", "Recurs weekly on %2", "Recurs every %1 weeks on %2", recur->frequency(), dayNames ); } case Recurrence::rMonthlyPos: { KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]" " weekdayname until end-date", "Recurs every month on the %2 %3 until %4", "Recurs every %1 months on the %2 %3 until %4", recur->frequency(), dayList[rule.pos() + 31], - calSys->weekDayName( rule.day(),KCalendarSystem::LongDayName ), + calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname", "Recurs every month on the %2 %3", "Recurs every %1 months on the %2 %3", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) ); } case Recurrence::rMonthlyDay: { int days = recur->monthDays()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date", "Recurs monthly on the %2 day until %3", "Recurs every %1 months on the %2 day until %3", recur->frequency(), dayList[days + 31], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs monthly on the [1st|2nd|...] day", "Recurs monthly on the %2 day", "Recurs every %1 month on the %2 day", recur->frequency(), dayList[days + 31] ); } case Recurrence::rYearlyMonth: { if ( recur->duration() != -1 ) { - txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]" - " until end-date", - "Recurs yearly on %2 %3 until %4", - "Recurs every %1 years on %2 %3 until %4", - recur->frequency(), - calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), - dayList[ recur->yearDates()[0] + 31 ], - recurEnd( incidence ) ); - if ( recur->duration() > 0 ) { - txt += i18nc( "number of occurrences", - " (%1 occurrences)", - recur->duration() ); + if ( !recur->yearDates().isEmpty() ) { + txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]" + " until end-date", + "Recurs yearly on %2 %3 until %4", + "Recurs every %1 years on %2 %3 until %4", + recur->frequency(), + calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), + dayList[ recur->yearDates()[0] + 31 ], + recurEnd( incidence ) ); + if ( recur->duration() > 0 ) { + txt += i18nc( "number of occurrences", + " (%1 occurrences)", + recur->duration() ); + } + return txt; } - return txt; } if ( !recur->yearDates().isEmpty() ) { return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]", "Recurs yearly on %2 %3", "Recurs every %1 years on %2 %3", recur->frequency(), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->yearDates()[0] + 31 ] ); } else { if (!recur->yearMonths().isEmpty() ) { return i18nc( "Recurs Every year on month-name [1st|2nd|...]", "Recurs yearly on %1 %2", calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), dayList[ recur->startDate().day() + 31 ] ); } else { return i18nc( "Recurs Every year on month-name [1st|2nd|...]", "Recurs yearly on %1 %2", calSys->monthName( recur->startDate().month(), recur->startDate().year() ), dayList[ recur->startDate().day() + 31 ] ); } } } case Recurrence::rYearlyDay: if ( recur->duration() != -1 ) { txt = i18ncp( "Recurs every N years on day N until end-date", "Recurs every year on day %2 until %3", "Recurs every %1 years" " on day %2 until %3", recur->frequency(), recur->yearDays()[0], recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Recurs every N YEAR[S] on day N", "Recurs every year on day %2", "Recurs every %1 years" " on day %2", recur->frequency(), recur->yearDays()[0] ); case Recurrence::rYearlyPos: { KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0]; if ( recur->duration() != -1 ) { txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " "of monthname until end-date", "Every year on the %2 %3 of %4 until %5", "Every %1 years on the %2 %3 of %4" " until %5", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), recurEnd( incidence ) ); if ( recur->duration() > 0 ) { txt += i18nc( "number of occurrences", " (%1 occurrences)", recur->duration() ); } return txt; } return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " "of monthname", "Every year on the %2 %3 of %4", "Every %1 years on the %2 %3 of %4", recur->frequency(), dayList[rule.pos() + 31], calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ); } default: return i18n( "Incidence recurs" ); } } QString IncidenceFormatter::timeToString( const KDateTime &date, bool shortfmt, const KDateTime::Spec &spec ) { if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone; } else { return KGlobal::locale()->formatTime( date.time(), !shortfmt ); } } QString IncidenceFormatter::dateToString( const KDateTime &date, bool shortfmt, const KDateTime::Spec &spec ) { if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; } else { return KGlobal::locale()->formatDate( date.date(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); } } QString IncidenceFormatter::dateTimeToString( const KDateTime &date, bool allDay, bool shortfmt, const KDateTime::Spec &spec ) { if ( allDay ) { return dateToString( date, shortfmt, spec ); } if ( spec.isValid() ) { QString timeZone; if ( spec.timeZone() != KSystemTimeZones::local() ) { timeZone = ' ' + spec.timeZone().name(); } return KGlobal::locale()->formatDateTime( date.toTimeSpec( spec ).dateTime(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; } else { return KGlobal::locale()->formatDateTime( date.dateTime(), ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); } } diff --git a/kcal/scheduler.cpp b/kcal/scheduler.cpp index 6659a58ab..a037478dd 100644 --- a/kcal/scheduler.cpp +++ b/kcal/scheduler.cpp @@ -1,604 +1,613 @@ /* This file is part of the kcal library. Copyright (c) 2001,2004 Cornelius Schumacher Copyright (C) 2004 Reinhold Kainhofer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is 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 "scheduler.h" #include "calendar.h" #include "event.h" #include "todo.h" #include "freebusy.h" #include "freebusycache.h" #include "icalformat.h" +#include "assignmentvisitor.h" #include #include #include #include using namespace KCal; //@cond PRIVATE class KCal::ScheduleMessage::Private { public: Private() {} IncidenceBase *mIncidence; iTIPMethod mMethod; Status mStatus; QString mError; }; //@endcond ScheduleMessage::ScheduleMessage( IncidenceBase *incidence, iTIPMethod method, ScheduleMessage::Status status ) : d( new KCal::ScheduleMessage::Private ) { d->mIncidence = incidence; d->mMethod = method; d->mStatus = status; } ScheduleMessage::~ScheduleMessage() { delete d; } IncidenceBase *ScheduleMessage::event() { return d->mIncidence; } iTIPMethod ScheduleMessage::method() { return d->mMethod; } ScheduleMessage::Status ScheduleMessage::status() { return d->mStatus; } QString ScheduleMessage::statusName( ScheduleMessage::Status status ) { switch( status ) { case PublishNew: return i18nc( "@item new message posting", "New Message Publish" ); case PublishUpdate: return i18nc( "@item updated message", "Updated Message Published" ); case Obsolete: return i18nc( "@item obsolete status", "Obsolete" ); case RequestNew: return i18nc( "@item request new message posting", "Request New Message" ); case RequestUpdate: return i18nc( "@item request updated posting", "Request Updated Message" ); default: return i18nc( "@item unknown status", "Unknown Status: %1", status ); } } QString ScheduleMessage::error() { return d->mError; } //@cond PRIVATE struct KCal::Scheduler::Private { Private() : mFreeBusyCache( 0 ) { } FreeBusyCache *mFreeBusyCache; }; //@endcond Scheduler::Scheduler( Calendar *calendar ) : d( new KCal::Scheduler::Private ) { mCalendar = calendar; mFormat = new ICalFormat(); mFormat->setTimeSpec( calendar->timeSpec() ); } Scheduler::~Scheduler() { delete mFormat; delete d; } void Scheduler::setFreeBusyCache( FreeBusyCache *c ) { d->mFreeBusyCache = c; } FreeBusyCache *Scheduler::freeBusyCache() const { return d->mFreeBusyCache; } bool Scheduler::acceptTransaction( IncidenceBase *incidence, iTIPMethod method, ScheduleMessage::Status status ) { return acceptTransaction( incidence, method, status, QString() ); } bool Scheduler::acceptTransaction( IncidenceBase *incidence, iTIPMethod method, ScheduleMessage::Status status, const QString &email ) { kDebug() << "method=" << methodName( method ); switch ( method ) { case iTIPPublish: return acceptPublish( incidence, status, method ); case iTIPRequest: return acceptRequest( incidence, status, email ); case iTIPAdd: return acceptAdd( incidence, status ); case iTIPCancel: return acceptCancel( incidence, status ); case iTIPDeclineCounter: return acceptDeclineCounter( incidence, status ); case iTIPReply: return acceptReply( incidence, status, method ); case iTIPRefresh: return acceptRefresh( incidence, status ); case iTIPCounter: return acceptCounter( incidence, status ); default: break; } deleteTransaction( incidence ); return false; } QString Scheduler::methodName( iTIPMethod method ) { switch ( method ) { case iTIPPublish: return QLatin1String( "Publish" ); case iTIPRequest: return QLatin1String( "Request" ); case iTIPRefresh: return QLatin1String( "Refresh" ); case iTIPCancel: return QLatin1String( "Cancel" ); case iTIPAdd: return QLatin1String( "Add" ); case iTIPReply: return QLatin1String( "Reply" ); case iTIPCounter: return QLatin1String( "Counter" ); case iTIPDeclineCounter: return QLatin1String( "Decline Counter" ); default: return QLatin1String( "Unknown" ); } } QString Scheduler::translatedMethodName( iTIPMethod method ) { switch ( method ) { case iTIPPublish: return i18nc( "@item event, to-do, journal or freebusy posting", "Publish" ); case iTIPRequest: return i18nc( "@item event, to-do or freebusy scheduling requests", "Request" ); case iTIPReply: return i18nc( "@item event, to-do or freebusy reply to request", "Reply" ); case iTIPAdd: return i18nc( "@item event, to-do or journal additional property request", "Add" ); case iTIPCancel: return i18nc( "@item event, to-do or journal cancellation notice", "Cancel" ); case iTIPRefresh: return i18nc( "@item event or to-do description update request", "Refresh" ); case iTIPCounter: return i18nc( "@item event or to-do submit counter proposal", "Counter" ); case iTIPDeclineCounter: return i18nc( "@item event or to-do decline a counter proposal", "Decline Counter" ); default: return i18nc( "@item no method", "Unknown" ); } } bool Scheduler::deleteTransaction(IncidenceBase *) { return true; } bool Scheduler::acceptPublish( IncidenceBase *newIncBase, ScheduleMessage::Status status, iTIPMethod method ) { if( newIncBase->type() == "FreeBusy" ) { return acceptFreeBusy( newIncBase, method ); } bool res = false; kDebug() << "status=" << ScheduleMessage::statusName( status ); Incidence *newInc = static_cast( newIncBase ); Incidence *calInc = mCalendar->incidence( newIncBase->uid() ); switch ( status ) { case ScheduleMessage::Unknown: case ScheduleMessage::PublishNew: case ScheduleMessage::PublishUpdate: - res = true; - if ( calInc ) { + if ( calInc && newInc ) { if ( ( newInc->revision() > calInc->revision() ) || ( newInc->revision() == calInc->revision() && newInc->lastModified() > calInc->lastModified() ) ) { - mCalendar->deleteIncidence( calInc ); - } else { - res = false; + AssignmentVisitor visitor; + if ( !visitor.assign( calInc, newInc ) ) { + kError() << "assigning different incidence types"; + } else { + res = true; + } } } - if ( res ) { - mCalendar->addIncidence( newInc ); - } break; case ScheduleMessage::Obsolete: res = true; break; default: break; } deleteTransaction( newIncBase ); return res; } bool Scheduler::acceptRequest( IncidenceBase *incidence, ScheduleMessage::Status status ) { return acceptRequest( incidence, status, QString() ); } bool Scheduler::acceptRequest( IncidenceBase *incidence, ScheduleMessage::Status status, const QString &email ) { Incidence *inc = static_cast( incidence ); if ( !inc ) { return false; } if ( inc->type() == "FreeBusy" ) { // reply to this request is handled in korganizer's incomingdialog return true; } const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() ); kDebug() << "status=" << ScheduleMessage::statusName( status ) << ": found " << existingIncidences.count() << " incidences with schedulingID " << inc->schedulingID(); Incidence::List::ConstIterator incit = existingIncidences.begin(); for ( ; incit != existingIncidences.end() ; ++incit ) { Incidence *i = *incit; kDebug() << "Considering this found event (" << ( i->isReadOnly() ? "readonly" : "readwrite" ) << ") :" << mFormat->toString( i ); // If it's readonly, we can't possible update it. if ( i->isReadOnly() ) { continue; } if ( i->revision() <= inc->revision() ) { // The new incidence might be an update for the found one bool isUpdate = true; // Code for new invitations: // If you think we could check the value of "status" to be RequestNew: we can't. // It comes from a similar check inside libical, where the event is compared to // other events in the calendar. But if we have another version of the event around // (e.g. shared folder for a group), the status could be RequestNew, Obsolete or Updated. kDebug() << "looking in " << i->uid() << "'s attendees"; // This is supposed to be a new request, not an update - however we want to update // the existing one to handle the "clicking more than once on the invitation" case. // So check the attendee status of the attendee. const KCal::Attendee::List attendees = i->attendees(); KCal::Attendee::List::ConstIterator ait; for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) { if( (*ait)->email() == email && (*ait)->status() == Attendee::NeedsAction ) { // This incidence wasn't created by me - it's probably in a shared folder // and meant for someone else, ignore it. kDebug() << "ignoring " << i->uid() << " since I'm still NeedsAction there"; isUpdate = false; break; } } if ( isUpdate ) { if ( i->revision() == inc->revision() && i->lastModified() > inc->lastModified() ) { // This isn't an update - the found incidence was modified more recently kDebug() << "This isn't an update - the found incidence was modified more recently"; deleteTransaction( i ); return false; } kDebug() << "replacing existing incidence " << i->uid(); - mCalendar->deleteIncidence( i ); - break; // replacing one is enough + bool res = true; + AssignmentVisitor visitor; + const QString oldUid = i->uid(); + if ( !visitor.assign( i, inc ) ) { + kError() << "assigning different incidence types"; + res = false; + } + i->setUid( oldUid ); + i->setSchedulingID( inc->uid() ); + deleteTransaction( incidence ); + return res; } } else { // This isn't an update - the found incidence has a bigger revision number kDebug() << "This isn't an update - the found incidence has a bigger revision number"; deleteTransaction(incidence); return false; } } // Move the uid to be the schedulingID and make a unique UID inc->setSchedulingID( inc->uid() ); inc->setUid( CalFormat::createUniqueId() ); // in case this is an update and we didn't find the to-be-updated incidence, // ask whether we should create a new one, or drop the update if ( existingIncidences.count() > 0 || inc->revision() == 0 || KMessageBox::warningYesNo( 0, i18nc( "@info", "The event, to-do or journal to be updated could not be found. " "Maybe it has already been deleted, or the calendar that " "contains it is disabled. Press 'Store' to create a new " "one or 'Throw away' to discard this update." ), i18nc( "@title", "Discard this update?" ), KGuiItem( i18nc( "@option", "Store" ) ), KGuiItem( i18nc( "@option", "Throw away" ) ) ) == KMessageBox::Yes ) { kDebug() << "Storing new incidence with scheduling uid=" << inc->schedulingID() << " and uid=" << inc->uid(); mCalendar->addIncidence( inc ); } deleteTransaction(incidence); return true; } bool Scheduler::acceptAdd( IncidenceBase *incidence, ScheduleMessage::Status /* status */) { deleteTransaction(incidence); return false; } bool Scheduler::acceptCancel( IncidenceBase *incidence, ScheduleMessage::Status /* status */) { const IncidenceBase *toDelete = mCalendar->incidenceFromSchedulingID( incidence->uid() ); bool ret = true; if ( toDelete ) { if ( toDelete->type() == "Event" ) { Event *event = mCalendar->event( toDelete->uid() ); ret = ( event && mCalendar->deleteEvent( event ) ); } else if ( toDelete->type() == "Todo" ) { Todo *todo = mCalendar->todo( toDelete->uid() ); ret = ( todo && mCalendar->deleteTodo( todo ) ); } } else { // only complain if we failed to determine the toDelete incidence // on non-initial request. Incidence *inc = static_cast( incidence ); if ( inc->revision() > 0 ) { ret = false; } } if ( !ret ) { KMessageBox::error( 0, i18n( "The event or task to be canceled could not be removed from your calendar. " "Maybe it has already been deleted, or the calendar that " "contains it is disabled." ) ); } deleteTransaction(incidence); return ret; } bool Scheduler::acceptDeclineCounter( IncidenceBase *incidence, ScheduleMessage::Status status ) { Q_UNUSED( status ); deleteTransaction( incidence ); return false; } bool Scheduler::acceptReply( IncidenceBase *incidence, ScheduleMessage::Status status, iTIPMethod method ) { Q_UNUSED( status ); if ( incidence->type() == "FreeBusy" ) { return acceptFreeBusy( incidence, method ); } bool ret = false; Event *ev = mCalendar->event( incidence->uid() ); Todo *to = mCalendar->todo( incidence->uid() ); // try harder to find the correct incidence if ( !ev && !to ) { const Incidence::List list = mCalendar->incidences(); for ( Incidence::List::ConstIterator it=list.constBegin(), end=list.constEnd(); it != end; ++it ) { if ( (*it)->schedulingID() == incidence->uid() ) { ev = dynamic_cast( *it ); to = dynamic_cast( *it ); break; } } } if ( ev || to ) { //get matching attendee in calendar kDebug() << "match found!"; Attendee::List attendeesIn = incidence->attendees(); Attendee::List attendeesEv; Attendee::List attendeesNew; if ( ev ) { attendeesEv = ev->attendees(); } if ( to ) { attendeesEv = to->attendees(); } Attendee::List::ConstIterator inIt; Attendee::List::ConstIterator evIt; for ( inIt = attendeesIn.constBegin(); inIt != attendeesIn.constEnd(); ++inIt ) { Attendee *attIn = *inIt; bool found = false; for ( evIt = attendeesEv.constBegin(); evIt != attendeesEv.constEnd(); ++evIt ) { Attendee *attEv = *evIt; if ( attIn->email().toLower() == attEv->email().toLower() ) { //update attendee-info kDebug() << "update attendee"; attEv->setStatus( attIn->status() ); attEv->setDelegate( attIn->delegate() ); attEv->setDelegator( attIn->delegator() ); ret = true; found = true; } } if ( !found && attIn->status() != Attendee::Declined ) { attendeesNew.append( attIn ); } } bool attendeeAdded = false; for ( Attendee::List::ConstIterator it = attendeesNew.constBegin(); it != attendeesNew.constEnd(); ++it ) { Attendee *attNew = *it; QString msg = i18nc( "@info", "%1 wants to attend %2 but was not invited.", attNew->fullName(), ( ev ? ev->summary() : to->summary() ) ); if ( !attNew->delegator().isEmpty() ) { msg = i18nc( "@info", "%1 wants to attend %2 on behalf of %3.", attNew->fullName(), ( ev ? ev->summary() : to->summary() ), attNew->delegator() ); } if ( KMessageBox::questionYesNo( 0, msg, i18nc( "@title", "Uninvited attendee" ), KGuiItem( i18nc( "@option", "Accept Attendance" ) ), KGuiItem( i18nc( "@option", "Reject Attendance" ) ) ) != KMessageBox::Yes ) { KCal::Incidence *cancel = dynamic_cast( incidence ); if ( cancel ) { cancel->addComment( i18nc( "@info", "The organizer rejected your attendance at this meeting." ) ); } performTransaction( cancel ? cancel : incidence, iTIPCancel, attNew->fullName() ); // ### can't delete cancel here because it is aliased to incidence which // is accessed in the next loop iteration (CID 4232) // delete cancel; continue; } Attendee *a = new Attendee( attNew->name(), attNew->email(), attNew->RSVP(), attNew->status(), attNew->role(), attNew->uid() ); a->setDelegate( attNew->delegate() ); a->setDelegator( attNew->delegator() ); if ( ev ) { ev->addAttendee( a ); } else if ( to ) { to->addAttendee( a ); } ret = true; attendeeAdded = true; } // send update about new participants if ( attendeeAdded ) { if ( ev ) { ev->setRevision( ev->revision() + 1 ); performTransaction( ev, iTIPRequest ); } if ( to ) { to->setRevision( to->revision() + 1 ); performTransaction( to, iTIPRequest ); } } if ( ret ) { // We set at least one of the attendees, so the incidence changed // Note: This should not result in a sequence number bump if ( ev ) { ev->updated(); } else if ( to ) { to->updated(); } } if ( to ) { // for VTODO a REPLY can be used to update the completion status of // a to-do. see RFC2446 3.4.3 Todo *update = dynamic_cast ( incidence ); Q_ASSERT( update ); if ( update && ( to->percentComplete() != update->percentComplete() ) ) { to->setPercentComplete( update->percentComplete() ); to->updated(); } } } else { kError(5800) << "No incidence for scheduling\n"; } if ( ret ) { deleteTransaction( incidence ); } return ret; } bool Scheduler::acceptRefresh( IncidenceBase *incidence, ScheduleMessage::Status status ) { Q_UNUSED( status ); // handled in korganizer's IncomingDialog deleteTransaction( incidence ); return false; } bool Scheduler::acceptCounter( IncidenceBase *incidence, ScheduleMessage::Status status ) { Q_UNUSED( status ); deleteTransaction( incidence ); return false; } bool Scheduler::acceptFreeBusy( IncidenceBase *incidence, iTIPMethod method ) { if ( !d->mFreeBusyCache ) { kError() << "KCal::Scheduler: no FreeBusyCache."; return false; } FreeBusy *freebusy = static_cast(incidence); kDebug() << "freeBusyDirName:" << freeBusyDir(); Person from; if( method == iTIPPublish ) { from = freebusy->organizer(); } if ( ( method == iTIPReply ) && ( freebusy->attendeeCount() == 1 ) ) { Attendee *attendee = freebusy->attendees().first(); from.setName( attendee->name() ); from.setEmail( attendee->email() ); } if ( !d->mFreeBusyCache->saveFreeBusy( freebusy, from ) ) { return false; } deleteTransaction( incidence ); return true; } diff --git a/kmime/CMakeLists.txt b/kmime/CMakeLists.txt index a87dd2580..1f928e872 100644 --- a/kmime/CMakeLists.txt +++ b/kmime/CMakeLists.txt @@ -1,55 +1,53 @@ add_subdirectory( tests ) -include_directories( ${CMAKE_SOURCE_DIR}/libkdepim/ ) - include(CheckTimezone) configure_file (config-kmime.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kmime.h) ########### next target ############### set(kmime_LIB_SRCS kmime_charfreq.cpp kmime_util.cpp kmime_mdn.cpp kmime_codecs.cpp kmime_codec_base64.cpp kmime_codec_uuencode.cpp kmime_codec_qp.cpp kmime_codec_identity.cpp kmime_parsers.cpp kmime_header_parsing.cpp kmime_headerfactory.cpp kmime_content.cpp kmime_contentindex.cpp kmime_headers.cpp kmime_message.cpp kmime_newsarticle.cpp kmime_dateformatter.cpp boolflags.cpp kautodeletehash.cpp ) kde4_add_library(kmime SHARED ${kmime_LIB_SRCS}) target_link_libraries(kmime ${KDE4_KDECORE_LIBS} ) set_target_properties(kmime PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}) install(TARGETS kmime EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) install( FILES boolflags.h kmime_export.h kmime_charfreq.h kmime_codecs.h kmime_content.h kmime_contentindex.h kmime_header_parsing.h kmime_headerfactory.h kmime_headers.h kmime_message.h kmime_mdn.h kmime_newsarticle.h kmime_dateformatter.h kmime_util.h DESTINATION ${INCLUDE_INSTALL_DIR}/kmime COMPONENT Devel) diff --git a/kmime/kmime_charfreq.cpp b/kmime/kmime_charfreq.cpp index 2de7b69a3..3ec6d05dc 100644 --- a/kmime/kmime_charfreq.cpp +++ b/kmime/kmime_charfreq.cpp @@ -1,252 +1,252 @@ /* kmime_charfreq.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 API for handling MIME data and defines the CharFreq class. @brief Defines the CharFreq class. @authors Marc Mutz \ */ #include "kmime_charfreq.h" using namespace KMime; /** * Private class that helps to provide binary compatibility between releases. * @internal */ //@cond PRIVATE //class KMime::CharFreq::Private //{ // public: //}; //@endcond CharFreq::CharFreq( const QByteArray &buf ) : mNUL( 0 ), mCTL( 0 ), mCR( 0 ), mLF( 0 ), mCRLF( 0 ), mPrintable( 0 ), mEightBit( 0 ), mTotal( 0 ), mLineMin( 0xffffffff ), mLineMax( 0 ), mTrailingWS( false ), mLeadingFrom( false ) { if ( !buf.isEmpty() ) { count( buf.data(), buf.size() ); } } CharFreq::CharFreq( const char *buf, size_t len ) : mNUL( 0 ), mCTL( 0 ), mCR( 0 ), mLF( 0 ), mCRLF( 0 ), mPrintable( 0 ), mEightBit( 0 ), mTotal( 0 ), mLineMin( 0xffffffff ), mLineMax( 0 ), mTrailingWS( false ), mLeadingFrom( false ) { if ( buf && len > 0 ) { count( buf, len ); } } //@cond PRIVATE static inline bool isWS( char ch ) { return ( ch == '\t' || ch == ' ' ); } //@endcond void CharFreq::count( const char *it, size_t len ) { const char *end = it + len; uint currentLineLength = 0; // initialize the prevChar with LF so that From_ detection works w/o // special-casing: char prevChar = '\n'; char prevPrevChar = 0; for ( ; it != end ; ++it ) { ++currentLineLength; switch ( *it ) { case '\0': ++mNUL; break; case '\r': ++mCR; break; case '\n': ++mLF; if ( prevChar == '\r' ) { --currentLineLength; ++mCRLF; } if ( currentLineLength >= mLineMax ) { mLineMax = currentLineLength-1; } if ( currentLineLength <= mLineMin ) { mLineMin = currentLineLength-1; } if ( !mTrailingWS ) { if ( isWS( prevChar ) || ( prevChar == '\r' && isWS( prevPrevChar ) ) ) { mTrailingWS = true; } } currentLineLength = 0; break; case 'F': // check for lines starting with From_ if not found already: if ( !mLeadingFrom ) { if ( prevChar == '\n' && end - it >= 5 && !qstrncmp( "From ", it, 5 ) ) { mLeadingFrom = true; } } ++mPrintable; break; default: { uchar c = *it; if ( c == '\t' || ( c >= ' ' && c <= '~' ) ) { ++mPrintable; } else if ( c == 127 || c < ' ' ) { ++mCTL; } else { ++mEightBit; } } } prevPrevChar = prevChar; prevChar = *it; } // consider the length of the last line if ( currentLineLength >= mLineMax ) { mLineMax = currentLineLength; } if ( currentLineLength <= mLineMin ) { mLineMin = currentLineLength; } // check whether the last character is tab or space if ( isWS( prevChar ) ) { mTrailingWS = true; } mTotal = len; } bool CharFreq::isEightBitData() const { return type() == EightBitData; } bool CharFreq::isEightBitText() const { return type() == EightBitText; } bool CharFreq::isSevenBitData() const { return type() == SevenBitData; } bool CharFreq::isSevenBitText() const { return type() == SevenBitText; } bool CharFreq::hasTrailingWhitespace() const { return mTrailingWS; } bool CharFreq::hasLeadingFrom() const { return mLeadingFrom; } CharFreq::Type CharFreq::type() const { #if 0 qDebug( "Total: %d; NUL: %d; CTL: %d;\n" "CR: %d; LF: %d; CRLF: %d;\n" "lineMin: %d; lineMax: %d;\n" "printable: %d; eightBit: %d;\n" "trailing whitespace: %s;\n" "leading 'From ': %s;\n", total, NUL, CTL, CR, LF, CRLF, lineMin, lineMax, printable, eightBit, mTrailingWS ? "yes" : "no" , mLeadingFrom ? "yes" : "no" ); #endif if ( mNUL ) { // must be binary return Binary; } // doesn't contain NUL's: if ( mEightBit ) { if ( mLineMax > 988 ) { return EightBitData; // not allowed in 8bit } if ( ( mLF != mCRLF && mCRLF > 0 ) || mCR != mCRLF || controlCodesRatio() > 0.2 ) { return EightBitData; } return EightBitText; } // doesn't contain NUL's, nor 8bit chars: if ( mLineMax > 988 ) { return SevenBitData; } if ( ( mLF != mCRLF && mCRLF > 0 ) || mCR != mCRLF || controlCodesRatio() > 0.2 ) { return SevenBitData; } // no NUL, no 8bit chars, no excessive CTLs and no lines > 998 chars: return SevenBitText; } float CharFreq::printableRatio() const { if ( mTotal ) { return float(mPrintable) / float(mTotal); } else { return 0; } } float CharFreq::controlCodesRatio() const { if ( mTotal ) { return float(mCTL) / float(mTotal); } else { return 0; } } diff --git a/kmime/kmime_charfreq.h b/kmime/kmime_charfreq.h index 94baf266f..46cfc8140 100644 --- a/kmime/kmime_charfreq.h +++ b/kmime/kmime_charfreq.h @@ -1,185 +1,185 @@ /* -*- c++ -*- kmime_charfreq.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 API for handling @ref MIME data and defines the CharFreq class. @brief Defines the CharFreq class. @authors Marc Mutz \ @glossary @anchor Eight-Bit @anchor eight-bit @b 8-bit: Data that contains bytes with at least one value greater than 127, or at least one NUL byte. @glossary @anchor Eight-Bit-Binary @anchor eight-bit-binary @b 8-bit-binary: Eight-bit data that contains a high percentage of non-ascii values, or lines longer than 998 characters, or stray CRs, or NULs. @glossary @anchor Eight-Bit-Text @anchor eight-bit-text @b 8-bit-text: Eight-bit data that contains a high percentage of ascii values, no lines longer than 998 characters, no NULs, and either only LFs or only CRLFs. @glossary @anchor Seven-Bit @anchor seven-bit @b 7-Bit: Data that contains bytes with all values less than 128, and no NULs. @glossary @anchor Seven-Bit-Binary @anchor seven-bit-binary @b 7-bit-binary: Seven-bit data that contains a high percentage of non-ascii values, or lines longer than 998 characters, or stray CRs. @glossary @anchor Seven-Bit-Text @anchor seven-bit-text @b 7-bit-text: Seven-bit data that contains a high percentage of ascii values, no lines longer than 998 characters, and either only LFs, or only CRLFs. */ #ifndef __KMIME_CHARFREQ_H__ #define __KMIME_CHARFREQ_H__ #include #include "kmime_export.h" #undef None namespace KMime { /** @brief A class for performing basic data typing using frequency count heuristics. This class performs character frequency counts on the provided data which are used in heuristics to determine a basic data type. The data types are: - @ref Eight-Bit-Binary - @ref Eight-Bit-Text - @ref Seven-Bit-Binary - @ref Seven-Bit-Text */ class KMIME_EXPORT CharFreq { public: /** Constructs a Character Frequency instance for a buffer @p buf of QByteArray data. @param buf is a QByteArray containing the data. */ explicit CharFreq( const QByteArray &buf ); /** Constructs a Character Frequency instance for a buffer @p buf of chars of length @p len. @param buf is a pointer to a character string containing the data. @param len is the length of @p buf, in characters. */ CharFreq( const char *buf, size_t len ); /** The different types of data. */ enum Type { None = 0, /**< Unknown */ EightBitData, /**< 8bit binary */ Binary = EightBitData, /**< 8bit binary */ SevenBitData, /**< 7bit binary */ EightBitText, /**< 8bit text */ SevenBitText /**< 7bit text */ }; /** Returns the data #Type as derived from the class heuristics. */ Type type() const; /** Returns true if the data #Type is EightBitData; false otherwise. */ bool isEightBitData() const; /** Returns true if the data #Type is EightBitText; false otherwise. */ bool isEightBitText() const; /** Returns true if the data #Type is SevenBitData; false otherwise. */ bool isSevenBitData() const; /** Returns true if the data #Type is SevenBitText; false otherwise. */ bool isSevenBitText() const; /** Returns true if the data contains trailing whitespace. i.e., if any line ends with space (' ') or tab ('\\t'). */ bool hasTrailingWhitespace() const; /** Returns true if the data contains a line that starts with "From ". */ bool hasLeadingFrom() const; /** Returns the percentage of printable characters in the data. The result is undefined if the number of data characters is zero. */ float printableRatio() const; /** Returns the percentage of control code characters (CTLs) in the data. The result is undefined if the number of data characters is zero. */ float controlCodesRatio() const; private: //@cond PRIVATE uint mNUL; // count of NUL chars uint mCTL; // count of CTLs (incl. DEL, excl. CR, LF, HT) uint mCR; // count of CR chars uint mLF; // count of LF chars uint mCRLF; // count of LFs, preceded by CRs uint mPrintable; // count of printable US-ASCII chars (SPC..~) uint mEightBit; // count of other latin1 chars (those with 8th bit set) uint mTotal; // count of all chars uint mLineMin; // minimum line length uint mLineMax; // maximum line length bool mTrailingWS; // does the buffer contain trailing whitespace? bool mLeadingFrom; // does the buffer contain lines starting with "From "? //@endcond /** Performs the character frequency counts on the data. @param buf is a pointer to a character string containing the data. @param len is the length of @p buf, in characters. */ void count( const char *buf, size_t len ); }; } // namespace KMime #endif /* __KMIME_CHARFREQ_H__ */ diff --git a/kmime/kmime_codec_base64.cpp b/kmime/kmime_codec_base64.cpp index b26702114..edf666f41 100644 --- a/kmime/kmime_codec_base64.cpp +++ b/kmime/kmime_codec_base64.cpp @@ -1,418 +1,418 @@ /* -*- c++ -*- kmime_codec_base64.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 Marc Mutz 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 API for handling @ref MIME data and defines the @ref Base64 and @ref RFC2047B @ref Codec classes. @brief Defines the Base64Codec and Rfc2047BEncodingCodec classes. @authors Marc Mutz \ */ #include "kmime_codec_base64.h" #include #include using namespace KMime; namespace KMime { // codec for base64 as specified in RFC 2045 //class Base64Codec; //class Base64Decoder; //class Base64Encoder; // codec for the B encoding as specified in RFC 2047 //class Rfc2047BEncodingCodec; //class Rfc2047BEncodingEncoder; //class Rfc2047BEncodingDecoder; //@cond PRIVATE static const uchar base64DecodeMap[128] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64 }; static const char base64EncodeMap[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; //@endcond class Base64Decoder : public Decoder { uint mStepNo; uchar mOutbits; bool mSawPadding : 1; protected: friend class Base64Codec; Base64Decoder( bool withCRLF=false ) : Decoder( withCRLF ), mStepNo( 0 ), mOutbits( 0 ), mSawPadding( false ) {} public: virtual ~Base64Decoder() {} bool decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ); // ### really needs no finishing??? bool finish( char* &dcursor, const char * const dend ) { Q_UNUSED( dcursor ); Q_UNUSED( dend ); return true; } }; class Base64Encoder : public Encoder { uint mStepNo; /** number of already written base64-quartets on current line */ uint mWrittenPacketsOnThisLine; uchar mNextbits; bool mInsideFinishing : 1; protected: friend class Rfc2047BEncodingCodec; friend class Rfc2047BEncodingEncoder; friend class Base64Codec; Base64Encoder( bool withCRLF=false ) : Encoder( withCRLF ), mStepNo( 0 ), mWrittenPacketsOnThisLine( 0 ), mNextbits( 0 ), mInsideFinishing( false ) {} bool generic_finish( char* &dcursor, const char * const dend, bool withLFatEnd ); public: virtual ~Base64Encoder() {} bool encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ); bool finish( char* &dcursor, const char * const dend ); protected: bool writeBase64( uchar ch, char* &dcursor, const char * const dend ) { return write( base64EncodeMap[ ch ], dcursor, dend ); } }; class Rfc2047BEncodingEncoder : public Base64Encoder { protected: friend class Rfc2047BEncodingCodec; Rfc2047BEncodingEncoder( bool withCRLF=false ) : Base64Encoder( withCRLF ) {} public: bool encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ); bool finish( char* &dcursor, const char * const dend ); }; Encoder *Base64Codec::makeEncoder( bool withCRLF ) const { return new Base64Encoder( withCRLF ); } Decoder *Base64Codec::makeDecoder( bool withCRLF ) const { return new Base64Decoder( withCRLF ); } Encoder *Rfc2047BEncodingCodec::makeEncoder( bool withCRLF ) const { return new Rfc2047BEncodingEncoder( withCRLF ); } /********************************************************/ /********************************************************/ /********************************************************/ bool Base64Decoder::decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) { while ( dcursor != dend && scursor != send ) { uchar ch = *scursor++; uchar value; // try converting ch to a 6-bit value: if ( ch < 128 ) { value = base64DecodeMap[ ch ]; } else { value = 64; } // ch isn't of the base64 alphabet, check for other significant chars: if ( value >= 64 ) { if ( ch == '=' ) { // padding: if ( mStepNo == 0 || mStepNo == 1 ) { if ( !mSawPadding ) { // malformed kWarning() << "Base64Decoder: unexpected padding" "character in input stream"; } mSawPadding = true; break; } else if ( mStepNo == 2 ) { // ok, there should be another one } else if ( mStepNo == 3 ) { // ok, end of encoded stream mSawPadding = true; break; } mSawPadding = true; mStepNo = (mStepNo + 1) % 4; continue; } else { // non-base64 alphabet continue; } } if ( mSawPadding ) { kWarning() << "Base64Decoder: Embedded padding character" "encountered!"; return true; } // add the new bits to the output stream and flush full octets: switch ( mStepNo ) { case 0: mOutbits = value << 2; break; case 1: *dcursor++ = (char)(mOutbits | value >> 4); mOutbits = value << 4; break; case 2: *dcursor++ = (char)(mOutbits | value >> 2); mOutbits = value << 6; break; case 3: *dcursor++ = (char)(mOutbits | value); mOutbits = 0; break; default: assert( 0 ); } mStepNo = (mStepNo + 1) % 4; } // return false when caller should call us again: return scursor == send; } // Base64Decoder::decode() bool Base64Encoder::encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) { const uint maxPacketsPerLine = 76 / 4; // detect when the caller doesn't adhere to our rules: if ( mInsideFinishing ) { return true; } while ( scursor != send && dcursor != dend ) { // properly empty the output buffer before starting something new: // ### fixme: we can optimize this away, since the buffer isn't // written to anyway (most of the time) if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) { return scursor == send; } uchar ch = *scursor++; // mNextbits // (part of) value of next sextet // check for line length; if ( mStepNo == 0 && mWrittenPacketsOnThisLine >= maxPacketsPerLine ) { writeCRLF( dcursor, dend ); mWrittenPacketsOnThisLine = 0; } // depending on mStepNo, extract value and mNextbits from the // octet stream: switch ( mStepNo ) { case 0: assert( mNextbits == 0 ); writeBase64( ch >> 2, dcursor, dend ); // top-most 6 bits -> output mNextbits = (ch & 0x3) << 4; // 0..1 bits -> 4..5 in mNextbits break; case 1: assert( (mNextbits & ~0x30) == 0 ); writeBase64( mNextbits | ch >> 4, dcursor, dend ); // 4..7 bits -> 0..3 in value mNextbits = (ch & 0xf) << 2; // 0..3 bits -> 2..5 in mNextbits break; case 2: assert( (mNextbits & ~0x3C) == 0 ); writeBase64( mNextbits | ch >> 6, dcursor, dend ); // 6..7 bits -> 0..1 in value writeBase64( ch & 0x3F, dcursor, dend ); // 0..5 bits -> output mNextbits = 0; mWrittenPacketsOnThisLine++; break; default: assert( 0 ); } mStepNo = ( mStepNo + 1 ) % 3; } if ( mOutputBufferCursor ) { flushOutputBuffer( dcursor, dend ); } return scursor == send; } bool Rfc2047BEncodingEncoder::encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) { // detect when the caller doesn't adhere to our rules: if ( mInsideFinishing ) { return true; } while ( scursor != send && dcursor != dend ) { // properly empty the output buffer before starting something new: // ### fixme: we can optimize this away, since the buffer isn't // written to anyway (most of the time) if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) { return scursor == send; } uchar ch = *scursor++; // mNextbits // (part of) value of next sextet // depending on mStepNo, extract value and mNextbits from the // octet stream: switch ( mStepNo ) { case 0: assert( mNextbits == 0 ); writeBase64( ch >> 2, dcursor, dend ); // top-most 6 bits -> output mNextbits = (ch & 0x3) << 4; // 0..1 bits -> 4..5 in mNextbits break; case 1: assert( (mNextbits & ~0x30) == 0 ); writeBase64( mNextbits | ch >> 4, dcursor, dend ); // 4..7 bits -> 0..3 in value mNextbits = (ch & 0xf) << 2; // 0..3 bits -> 2..5 in mNextbits break; case 2: assert( (mNextbits & ~0x3C) == 0 ); writeBase64( mNextbits | ch >> 6, dcursor, dend ); // 6..7 bits -> 0..1 in value writeBase64( ch & 0x3F, dcursor, dend ); // 0..5 bits -> output mNextbits = 0; break; default: assert( 0 ); } mStepNo = ( mStepNo + 1 ) % 3; } if ( mOutputBufferCursor ) { flushOutputBuffer( dcursor, dend ); } return scursor == send; } bool Base64Encoder::finish( char* &dcursor, const char * const dend ) { return generic_finish( dcursor, dend, true ); } bool Rfc2047BEncodingEncoder::finish( char* & dcursor, const char * const dend ) { return generic_finish( dcursor, dend, false ); } bool Base64Encoder::generic_finish( char* &dcursor, const char * const dend, bool withLFatEnd ) { if ( mInsideFinishing ) { return flushOutputBuffer( dcursor, dend ); } if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) { return false; } mInsideFinishing = true; // // writing out the last mNextbits... // switch ( mStepNo ) { case 1: // 2 mNextbits waiting to be written. Needs two padding chars: case 2: // 4 or 6 mNextbits waiting to be written. Completes a block writeBase64( mNextbits, dcursor, dend ); mNextbits = 0; break; case 0: // no padding, nothing to be written, except possibly the CRLF assert( mNextbits == 0 ); break; default: assert( 0 ); } // // adding padding... // switch( mStepNo ) { case 1: write( '=', dcursor, dend ); // fall through: case 2: write( '=', dcursor, dend ); // fall through: case 0: // completed an quartet - add CRLF if ( withLFatEnd ) { writeCRLF( dcursor, dend ); } return flushOutputBuffer( dcursor, dend ); default: assert( 0 ); } return true; // asserts get compiled out } } // namespace KMime diff --git a/kmime/kmime_codec_base64.h b/kmime/kmime_codec_base64.h index 2b6d62daf..7f9b25107 100644 --- a/kmime/kmime_codec_base64.h +++ b/kmime/kmime_codec_base64.h @@ -1,194 +1,194 @@ /* -*- c++ -*- kmime_codec_base64.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 API for handling @ref MIME data and defines the @ref Base64 and @ref RFC2047B @ref Codec classes. @brief Defines the Base64Codec and Rfc2047BEncodingCodec classes. @authors Marc Mutz \ @glossary @anchor Base64 @anchor base64 @b base64: a binary to text encoding scheme based on @ref RFC1421. @glossary @anchor RFC1421 @anchor rfc1421 @b RFC @b 1421: RFC that defines the Privacy Enhancement for Internet Electronic Mail: Part I: Message Encryption and Authentication Procedures. @glossary @anchor RFC2045 @anchor rfc2045 @b RFC @b 2045: RFC that defines the MIME Part One: Format of Internet Message Bodies. @glossary @anchor RFC2047 @anchor rfc2047 @b RFC @b 2047: RFC that defines the MIME Part Three: Message Header Extensions for Non-ASCII Text. @glossary @anchor RFC2047B @anchor rfc2047b @b RFC @b 2047B: Section 4.1 of @ref RFC2047. */ #ifndef __KMIME_CODEC_BASE64__ #define __KMIME_CODEC_BASE64__ #include "kmime_codecs.h" namespace KMime { /** @brief A class representing the @ref codec for @ref Base64 as specified in @ref RFC2045 */ class KMIME_EXPORT Base64Codec : public Codec { protected: friend class Codec; /** Constructs a Base64 codec. */ Base64Codec() : Codec() {} public: /** Destroys the codec. */ virtual ~Base64Codec() {} /** @copydoc Codec::name() */ const char *name() const { return "base64"; } /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { // first, the total number of 4-char packets will be: int totalNumPackets = ( insize + 2 ) / 3; // now, after every 76/4'th packet there needs to be a linebreak: int numLineBreaks = totalNumPackets / (76/4); // and at the very end, too: ++numLineBreaks; // putting it all together, we have: return 4 * totalNumPackets + ( withCRLF ? 2 : 1 ) * numLineBreaks; } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const { // assuming all characters are part of the base64 stream (which // does almost never hold due to required linebreaking; but // additional non-base64 chars don't affect the output size), each // 4-tupel of them becomes a 3-tupel in the decoded octet // stream. So: int result = ( ( insize + 3 ) / 4 ) * 3; // but all of them may be \n, so if ( withCRLF ) { result *= 2; // :-o } return result; } /** @copydoc Codec::makeEncoder() */ Encoder *makeEncoder( bool withCRLF=false ) const; /** @copydoc Codec::makeDecoder() */ Decoder *makeDecoder( bool withCRLF=false ) const; }; /** @brief A class representing the @ref codec for the B encoding as specified in @ref RFC2047B. */ class KMIME_EXPORT Rfc2047BEncodingCodec : public Base64Codec { protected: friend class Codec; /** Constructs a RFC2047B codec. */ Rfc2047BEncodingCodec() : Base64Codec() {} public: /** Destroys the codec. */ virtual ~Rfc2047BEncodingCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "b"; } /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { Q_UNUSED( withCRLF ); // Each (begun) 3-octet triple becomes a 4 char quartet, so: return ( ( insize + 2 ) / 3 ) * 4; } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const { Q_UNUSED( withCRLF ); // Each 4-char quartet becomes a 3-octet triple, the last one // possibly even less. So: return ( ( insize + 3 ) / 4 ) * 3; } /** @copydoc Codec::makeEncoder() */ Encoder *makeEncoder( bool withCRLF=false ) const; }; } // namespace KMime #endif // __KMIME_CODEC_BASE64__ diff --git a/kmime/kmime_codec_identity.cpp b/kmime/kmime_codec_identity.cpp index baae8a567..a9651a026 100644 --- a/kmime/kmime_codec_identity.cpp +++ b/kmime/kmime_codec_identity.cpp @@ -1,110 +1,110 @@ /* -*- c++ -*- kmime_codec_identity.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2004 Marc Mutz 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 API for handling @ref MIME data and defines the Identity, @ref seven-bit-text, @ref eight-bit-text, and @ref eight-bit-binary @ref Codec classes. @brief Defines the classes IdentityCodec, SevenBitCodec, EightBitCodec, and BinaryCodec. @authors Marc Mutz \ */ #include "kmime_codec_identity.h" #include #include #include #include #include using namespace KMime; namespace KMime { class IdentityEnDecoder : public Encoder, public Decoder { protected: friend class IdentityCodec; IdentityEnDecoder( bool withCRLF ): Encoder( false ) { kWarning( withCRLF, 5100 ) << "IdentityEnDecoder: withCRLF isn't yet supported!"; } public: ~IdentityEnDecoder() {} bool encode( const char* &scursor, const char *const send, char* &dcursor, const char *const dend ) { return decode( scursor, send, dcursor, dend ); } bool decode( const char* &scursor, const char *const send, char* &dcursor, const char *const dend ); bool finish( char* &dcursor, const char *const dend ) { Q_UNUSED( dcursor ); Q_UNUSED( dend ); return true; } }; Encoder *IdentityCodec::makeEncoder( bool withCRLF ) const { return new IdentityEnDecoder( withCRLF ); } Decoder *IdentityCodec::makeDecoder( bool withCRLF ) const { return new IdentityEnDecoder( withCRLF ); } /********************************************************/ /********************************************************/ /********************************************************/ bool IdentityEnDecoder::decode( const char* &scursor, const char *const send, char* &dcursor, const char *const dend ) { const int size = qMin( send - scursor, dcursor - dend ); if ( size > 0 ) { std::memmove( dcursor, scursor, size ); dcursor += size; scursor += size; } return scursor == send; } QByteArray IdentityCodec::encode( const QByteArray &src, bool withCRLF ) const { kWarning( withCRLF, 5100 ) << "IdentityCodec::encode(): withCRLF not yet supported!"; return src; } QByteArray IdentityCodec::decode( const QByteArray &src, bool withCRLF ) const { kWarning( withCRLF, 5100 ) << "IdentityCodec::decode(): withCRLF not yet supported!"; return src; } } // namespace KMime diff --git a/kmime/kmime_codec_identity.h b/kmime/kmime_codec_identity.h index cc1f10416..65f137ec7 100644 --- a/kmime/kmime_codec_identity.h +++ b/kmime/kmime_codec_identity.h @@ -1,214 +1,214 @@ /* -*- c++ -*- kmime_codec_identity.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2004 Marc Mutz 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 API for handling @ref MIME data and defines the Identity, @ref seven-bit-text, @ref eight-bit-text, and @ref eight-bit-binary @ref Codec classes. @brief Defines the classes IdentityCodec, SevenBitCodec, EightBitCodec, and BinaryCodec. @authors Marc Mutz \ */ #ifndef __KMIME_CODEC_IDENTITY_H__ #define __KMIME_CODEC_IDENTITY_H__ #include "kmime_codecs.h" class QByteArray; namespace KMime { /** @brief A class representing the Identify @ref codec. */ class KMIME_EXPORT IdentityCodec : public Codec { protected: friend class Codec; /** Constructs the Identity codec. */ IdentityCodec() : Codec() {} public: /** Destroys the codec. */ ~IdentityCodec() {} using Codec::encode; using Codec::decode; /** @copydoc QByteArray Codec::encode() */ QByteArray encode( const QByteArray &src, bool withCRLF=false ) const; /** @copydoc QByteArray Codec::decode() */ QByteArray decode( const QByteArray &src, bool withCRLF=false ) const; /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF ) const { if ( withCRLF ) { return 2 * insize; } else { return insize; } } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF ) const { if ( withCRLF ) { return 2 * insize; } else { return insize; } } /** @copydoc Codec::makeEncoder() */ Encoder *makeEncoder( bool withCRLF=false ) const; /** @copydoc Codec::makeDecoder() */ Decoder *makeDecoder( bool withCRLF=false ) const; }; /** @brief A class representing the @ref codec for @ref seven-bit-text. */ class KMIME_EXPORT SevenBitCodec : public IdentityCodec { protected: friend class Codec; /** Constructs the 7-bit codec. */ SevenBitCodec() : IdentityCodec() {} public: /** Destroys the codec. */ ~SevenBitCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "7bit"; } }; /** @brief A class representing the @ref codec for @ref eight-bit-text. */ class KMIME_EXPORT EightBitCodec : public IdentityCodec { protected: friend class Codec; /** Constructs the 8-bit codec. */ EightBitCodec() : IdentityCodec() {} public: /** Destroys the codec. */ ~EightBitCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "8bit"; } }; /** @brief A class representing the @ref codec for @ref eight-bit-binary. */ class KMIME_EXPORT BinaryCodec : public IdentityCodec { protected: friend class Codec; /** Constructs the 8-bit-binary codec. */ BinaryCodec() : IdentityCodec() {} public: /** Destroys the codec. */ ~BinaryCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "binary"; } /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { Q_UNUSED( withCRLF ); return insize; } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const { Q_UNUSED( withCRLF ); return insize; } }; } // namespace KMime #endif // __KMIME_CODEC_IDENTITY_H__ diff --git a/kmime/kmime_codec_qp.cpp b/kmime/kmime_codec_qp.cpp index b524188de..5706a3359 100644 --- a/kmime/kmime_codec_qp.cpp +++ b/kmime/kmime_codec_qp.cpp @@ -1,739 +1,739 @@ /* -*- c++ -*- kmime_codec_qp.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2002 Marc Mutz 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 API for handling @ref MIME data and defines the @ref QuotedPrintable, @ref RFC2047Q, and @ref RFC2231 @ref Codec classes. @brief Defines the classes QuotedPrintableCodec, Rfc2047QEncodingCodec, and Rfc2231EncodingCodec. @authors Marc Mutz \ */ #include "kmime_codec_qp.h" #include "kmime_util.h" #include #include using namespace KMime; namespace KMime { // some helpful functions: /** Converts a 4-bit @p value into its hexadecimal characater representation. So input of value [0,15] returns ['0','1',... 'F']. Input values greater than 15 will produce undesired results. @param value is an unsigned character containing the 4-bit input value. */ static inline char binToHex( uchar value ) { if ( value > 9 ) { return value + 'A' - 10; } else { return value + '0'; } } /** Returns the high-order 4 bits of an 8-bit value in another 8-bit value. @param ch is an unsigned character containing the 8-bit input value. */ static inline uchar highNibble( uchar ch ) { return ch >> 4; } /** Returns the low-order 4 bits of an 8-bit value in another 8-bit value. @param ch is an unsigned character containing the 8-bit input value. */ static inline uchar lowNibble( uchar ch ) { return ch & 0xF; } /** Returns true if the specified value is a not Control character or question mark; else true. @param ch is an unsigned character containing the 8-bit input value. */ static inline bool keep( uchar ch ) { // no CTLs, except HT and not '?' return !( ( ch < ' ' && ch != '\t' ) || ch == '?' ); } // // QuotedPrintableCodec // class QuotedPrintableEncoder : public Encoder { char mInputBuffer[16]; uchar mCurrentLineLength; // 0..76 uchar mAccu; uint mInputBufferReadCursor : 4; // 0..15 uint mInputBufferWriteCursor : 4; // 0..15 enum { Never, AtBOL, Definitely } mAccuNeedsEncoding : 2; bool mSawLineEnd : 1; bool mSawCR : 1; bool mFinishing : 1; bool mFinished : 1; protected: friend class QuotedPrintableCodec; QuotedPrintableEncoder( bool withCRLF=false ) : Encoder( withCRLF ), mCurrentLineLength( 0 ), mAccu( 0 ), mInputBufferReadCursor( 0 ), mInputBufferWriteCursor( 0 ), mAccuNeedsEncoding( Never ), mSawLineEnd( false ), mSawCR( false ), mFinishing( false ), mFinished( false ) {} bool needsEncoding( uchar ch ) { return ch > '~' || ( ch < ' ' && ch != '\t' ) || ch == '='; } bool needsEncodingAtEOL( uchar ch ) { return ch == ' ' || ch == '\t'; } bool needsEncodingAtBOL( uchar ch ) { return ch == 'F' || ch == '.' || ch == '-'; } bool fillInputBuffer( const char* &scursor, const char * const send ); bool processNextChar(); void createOutputBuffer( char* &dcursor, const char * const dend ); public: virtual ~QuotedPrintableEncoder() {} bool encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ); bool finish( char* &dcursor, const char * const dend ); }; class QuotedPrintableDecoder : public Decoder { const char mEscapeChar; char mBadChar; /** @p accu holds the msb nibble of the hexchar or zero. */ uchar mAccu; /** @p insideHexChar is true iff we're inside an hexchar (=XY). Together with @ref mAccu, we can build this states: @li @p insideHexChar == @p false: normal text @li @p insideHexChar == @p true, @p mAccu == 0: saw the leading '=' @li @p insideHexChar == @p true, @p mAccu != 0: saw the first nibble '=X' */ const bool mQEncoding; bool mInsideHexChar; bool mFlushing; bool mExpectLF; bool mHaveAccu; /** @p mLastChar holds the first char of an encoded char, so that we are able to keep the first char if the second char is invalid. */ char mLastChar; protected: friend class QuotedPrintableCodec; friend class Rfc2047QEncodingCodec; friend class Rfc2231EncodingCodec; QuotedPrintableDecoder( bool withCRLF=false, bool aQEncoding=false, char aEscapeChar='=' ) : Decoder( withCRLF ), mEscapeChar( aEscapeChar ), mBadChar( 0 ), mAccu( 0 ), mQEncoding( aQEncoding ), mInsideHexChar( false ), mFlushing( false ), mExpectLF( false ), mHaveAccu( false ), mLastChar( 0 ) {} public: virtual ~QuotedPrintableDecoder() {} bool decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ); bool finish( char* & dcursor, const char * const dend ); }; class Rfc2047QEncodingEncoder : public Encoder { uchar mAccu; uchar mStepNo; const char mEscapeChar; bool mInsideFinishing : 1; protected: friend class Rfc2047QEncodingCodec; friend class Rfc2231EncodingCodec; Rfc2047QEncodingEncoder( bool withCRLF=false, char aEscapeChar='=' ) : Encoder( withCRLF ), mAccu( 0 ), mStepNo( 0 ), mEscapeChar( aEscapeChar ), mInsideFinishing( false ) { // else an optimization in ::encode might break. assert( aEscapeChar == '=' || aEscapeChar == '%' ); } // this code assumes that isEText( mEscapeChar ) == false! bool needsEncoding( uchar ch ) { if ( ch > 'z' ) { return true; // {|}~ DEL and 8bit chars need } if ( !isEText( ch ) ) { return true; // all but a-zA-Z0-9!/*+- need, too } if ( mEscapeChar == '%' && ( ch == '*' || ch == '/' ) ) { return true; // not allowed in rfc2231 encoding } return false; } public: virtual ~Rfc2047QEncodingEncoder() {} bool encode( const char* & scursor, const char * const send, char* & dcursor, const char * const dend ); bool finish( char* & dcursor, const char * const dend ); }; // this doesn't access any member variables, so it can be defined static // but then we can't call it from virtual functions static int QuotedPrintableDecoder_maxDecodedSizeFor( int insize, bool withCRLF ) { // all chars unencoded: int result = insize; // but maybe all of them are \n and we need to make them \r\n :-o if ( withCRLF ) result += insize; // there might be an accu plus escape result += 2; return result; } Encoder *QuotedPrintableCodec::makeEncoder( bool withCRLF ) const { return new QuotedPrintableEncoder( withCRLF ); } Decoder *QuotedPrintableCodec::makeDecoder( bool withCRLF ) const { return new QuotedPrintableDecoder( withCRLF ); } int QuotedPrintableCodec::maxDecodedSizeFor( int insize, bool withCRLF ) const { return QuotedPrintableDecoder_maxDecodedSizeFor(insize, withCRLF); } Encoder *Rfc2047QEncodingCodec::makeEncoder( bool withCRLF ) const { return new Rfc2047QEncodingEncoder( withCRLF ); } Decoder *Rfc2047QEncodingCodec::makeDecoder( bool withCRLF ) const { return new QuotedPrintableDecoder( withCRLF, true ); } int Rfc2047QEncodingCodec::maxDecodedSizeFor( int insize, bool withCRLF ) const { return QuotedPrintableDecoder_maxDecodedSizeFor(insize, withCRLF); } Encoder *Rfc2231EncodingCodec::makeEncoder( bool withCRLF ) const { return new Rfc2047QEncodingEncoder( withCRLF, '%' ); } Decoder *Rfc2231EncodingCodec::makeDecoder( bool withCRLF ) const { return new QuotedPrintableDecoder( withCRLF, true, '%' ); } int Rfc2231EncodingCodec::maxDecodedSizeFor( int insize, bool withCRLF ) const { return QuotedPrintableDecoder_maxDecodedSizeFor(insize, withCRLF); } /********************************************************/ /********************************************************/ /********************************************************/ bool QuotedPrintableDecoder::decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) { if ( mWithCRLF ) { kWarning() << "CRLF output for decoders isn't yet supported!"; } while ( scursor != send && dcursor != dend ) { if ( mFlushing ) { // we have to flush chars in the aftermath of an decoding // error. The way to request a flush is to // - store the offending character in mBadChar and // - set mFlushing to true. // The supported cases are (H: hexchar, X: bad char): // =X, =HX, CR // mBadChar is only written out if it is not by itself illegal in // quoted-printable (e.g. CTLs, 8Bits). // A fast way to suppress mBadChar output is to set it to NUL. if ( mInsideHexChar ) { // output '=' *dcursor++ = mEscapeChar; mInsideHexChar = false; } else if ( mHaveAccu ) { // output the high nibble of the accumulator: *dcursor++ = mLastChar; mHaveAccu = false; mAccu = 0; } else { // output mBadChar assert( mAccu == 0 ); if ( mBadChar ) { if ( mBadChar == '=' ) { mInsideHexChar = true; } else { *dcursor++ = mBadChar; } mBadChar = 0; } mFlushing = false; } continue; } assert( mBadChar == 0 ); uchar ch = *scursor++; uchar value = 255; if ( mExpectLF && ch != '\n' ) { kWarning() << "QuotedPrintableDecoder:" "illegally formed soft linebreak or lonely CR!"; mInsideHexChar = false; mExpectLF = false; assert( mAccu == 0 ); } if ( mInsideHexChar ) { // next char(s) represent nibble instead of itself: if ( ch <= '9' ) { if ( ch >= '0' ) { value = ch - '0'; } else { switch ( ch ) { case '\r': mExpectLF = true; break; case '\n': // soft line break, but only if mAccu is NUL. if ( !mHaveAccu ) { mExpectLF = false; mInsideHexChar = false; break; } // else fall through default: kWarning() << "QuotedPrintableDecoder:" "illegally formed hex char! Outputting verbatim."; mBadChar = ch; mFlushing = true; } continue; } } else { // ch > '9' if ( ch <= 'F' ) { if ( ch >= 'A' ) { value = 10 + ch - 'A'; } else { // [:-@] mBadChar = ch; mFlushing = true; continue; } } else { // ch > 'F' if ( ch <= 'f' && ch >= 'a' ) { value = 10 + ch - 'a'; } else { mBadChar = ch; mFlushing = true; continue; } } } assert( value < 16 ); assert( mBadChar == 0 ); assert( !mExpectLF ); if ( mHaveAccu ) { *dcursor++ = char( mAccu | value ); mAccu = 0; mHaveAccu = false; mInsideHexChar = false; } else { mHaveAccu = true; mAccu = value << 4; mLastChar = ch; } } else { // not mInsideHexChar if ( ( ch <= '~' && ch >= ' ' ) || ch == '\t' ) { if ( ch == mEscapeChar ) { mInsideHexChar = true; } else if ( mQEncoding && ch == '_' ) { *dcursor++ = char( 0x20 ); } else { *dcursor++ = char( ch ); } } else if ( ch == '\n' ) { *dcursor++ = '\n'; mExpectLF = false; } else if ( ch == '\r' ) { mExpectLF = true; } else { kWarning() << "QuotedPrintableDecoder:" << ch << "illegal character in input stream! Ignoring."; } } } return scursor == send; } bool QuotedPrintableDecoder::finish( char* &dcursor, const char * const dend ) { while ( ( mInsideHexChar || mHaveAccu || mFlushing ) && dcursor != dend ) { // we have to flush chars if ( mInsideHexChar ) { // output '=' *dcursor++ = mEscapeChar; mInsideHexChar = false; } else if ( mHaveAccu ) { // output the high nibble of the accumulator: *dcursor++ = mLastChar; mHaveAccu = false; mAccu = 0; } else { // output mBadChar assert( mAccu == 0 ); if ( mBadChar ) { *dcursor++ = mBadChar; mBadChar = 0; } mFlushing = false; } } // return false if we are not finished yet; note that mInsideHexChar is always false return !( mHaveAccu || mFlushing ); } bool QuotedPrintableEncoder::fillInputBuffer( const char* &scursor, const char * const send ) { // Don't read more if there's still a tail of a line in the buffer: if ( mSawLineEnd ) { return true; } // Read until the buffer is full or we have found CRLF or LF (which // don't end up in the input buffer): for ( ; ( mInputBufferWriteCursor + 1 ) % 16 != mInputBufferReadCursor && scursor != send ; mInputBufferWriteCursor++ ) { char ch = *scursor++; if ( ch == '\r' ) { mSawCR = true; } else if ( ch == '\n' ) { // remove the CR from the input buffer (if any) and return that // we found a line ending: if ( mSawCR ) { mSawCR = false; assert( mInputBufferWriteCursor != mInputBufferReadCursor ); mInputBufferWriteCursor--; } mSawLineEnd = true; return true; // saw CRLF or LF } else { mSawCR = false; } mInputBuffer[ mInputBufferWriteCursor ] = ch; } mSawLineEnd = false; return false; // didn't see a line ending... } bool QuotedPrintableEncoder::processNextChar() { // If we process a buffer which doesn't end in a line break, we // can't process all of it, since the next chars that will be read // could be a line break. So we empty the buffer only until a fixed // number of chars is left (except when mFinishing, which means that // the data doesn't end in newline): const int minBufferFillWithoutLineEnd = 4; assert( mOutputBufferCursor == 0 ); int bufferFill = int( mInputBufferWriteCursor ) - int( mInputBufferReadCursor ) ; if ( bufferFill < 0 ) { bufferFill += 16; } assert( bufferFill >=0 && bufferFill <= 15 ); if ( !mFinishing && !mSawLineEnd && bufferFill < minBufferFillWithoutLineEnd ) { return false; } // buffer is empty, return false: if ( mInputBufferReadCursor == mInputBufferWriteCursor ) { return false; } // Real processing goes here: mAccu = mInputBuffer[ mInputBufferReadCursor++ ]; if ( needsEncoding( mAccu ) ) { // always needs encoding or mAccuNeedsEncoding = Definitely; } else if ( ( mSawLineEnd || mFinishing ) // needs encoding at end of line && bufferFill == 1 // or end of buffer && needsEncodingAtEOL( mAccu ) ) { mAccuNeedsEncoding = Definitely; } else if ( needsEncodingAtBOL( mAccu ) ) { mAccuNeedsEncoding = AtBOL; } else { // never needs encoding mAccuNeedsEncoding = Never; } return true; } // Outputs processed (verbatim or hex-encoded) chars and inserts soft // line breaks as necessary. Depends on processNextChar's directions // on whether or not to encode the current char, and whether or not // the current char is the last one in it's input line: void QuotedPrintableEncoder::createOutputBuffer( char* &dcursor, const char * const dend ) { const int maxLineLength = 76; // rfc 2045 assert( mOutputBufferCursor == 0 ); bool lastOneOnThisLine = mSawLineEnd && mInputBufferReadCursor == mInputBufferWriteCursor; int neededSpace = 1; if ( mAccuNeedsEncoding == Definitely ) { neededSpace = 3; } // reserve space for the soft hyphen (=) if ( !lastOneOnThisLine ) { neededSpace++; } if ( mCurrentLineLength > maxLineLength - neededSpace ) { // current line too short, insert soft line break: write( '=', dcursor, dend ); writeCRLF( dcursor, dend ); mCurrentLineLength = 0; } if ( Never == mAccuNeedsEncoding || ( AtBOL == mAccuNeedsEncoding && mCurrentLineLength != 0 ) ) { write( mAccu, dcursor, dend ); mCurrentLineLength++; } else { write( '=', dcursor, dend ); write( binToHex( highNibble( mAccu ) ), dcursor, dend ); write( binToHex( lowNibble( mAccu ) ), dcursor, dend ); mCurrentLineLength += 3; } } bool QuotedPrintableEncoder::encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) { // support probing by the caller: if ( mFinishing ) { return true; } while ( scursor != send && dcursor != dend ) { if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) { return scursor == send; } assert( mOutputBufferCursor == 0 ); // fill input buffer until eol has been reached or until the // buffer is full, whatever comes first: fillInputBuffer( scursor, send ); if ( processNextChar() ) { // there was one... createOutputBuffer( dcursor, dend ); } else if ( mSawLineEnd && mInputBufferWriteCursor == mInputBufferReadCursor ) { // load a hard line break into output buffer: writeCRLF( dcursor, dend ); // signal fillInputBuffer() we are ready for the next line: mSawLineEnd = false; mCurrentLineLength = 0; } else { // we are supposedly finished with this input block: break; } } // make sure we write as much as possible and don't stop _writing_ // just because we have no more _input_: if ( mOutputBufferCursor ) { flushOutputBuffer( dcursor, dend ); } return scursor == send; } // encode bool QuotedPrintableEncoder::finish( char* &dcursor, const char * const dend ) { mFinishing = true; if ( mFinished ) { return flushOutputBuffer( dcursor, dend ); } while ( dcursor != dend ) { if ( mOutputBufferCursor && !flushOutputBuffer( dcursor, dend ) ) { return false; } assert( mOutputBufferCursor == 0 ); if ( processNextChar() ) { // there was one... createOutputBuffer( dcursor, dend ); } else if ( mSawLineEnd && mInputBufferWriteCursor == mInputBufferReadCursor ) { // load a hard line break into output buffer: writeCRLF( dcursor, dend ); mSawLineEnd = false; mCurrentLineLength = 0; } else { mFinished = true; return flushOutputBuffer( dcursor, dend ); } } return mFinished && !mOutputBufferCursor; } // finish bool Rfc2047QEncodingEncoder::encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) { if ( mInsideFinishing ) { return true; } while ( scursor != send && dcursor != dend ) { uchar value; switch ( mStepNo ) { case 0: // read the next char and decide if and how do encode: mAccu = *scursor++; if ( !needsEncoding( mAccu ) ) { *dcursor++ = char( mAccu ); } else if ( mEscapeChar == '=' && mAccu == 0x20 ) { // shortcut encoding for 0x20 (latin-1/us-ascii SPACE) // (not for rfc2231 encoding) *dcursor++ = '_'; } else { // needs =XY encoding - write escape char: *dcursor++ = mEscapeChar; mStepNo = 1; } continue; case 1: // extract hi-nibble: value = highNibble( mAccu ); mStepNo = 2; break; case 2: // extract lo-nibble: value = lowNibble( mAccu ); mStepNo = 0; break; default: assert( 0 ); } // and write: *dcursor++ = binToHex( value ); } return scursor == send; } // encode #include bool Rfc2047QEncodingEncoder::finish( char* &dcursor, const char * const dend ) { mInsideFinishing = true; // write the last bits of mAccu, if any: while ( mStepNo != 0 && dcursor != dend ) { uchar value; switch ( mStepNo ) { case 1: // extract hi-nibble: value = highNibble( mAccu ); mStepNo = 2; break; case 2: // extract lo-nibble: value = lowNibble( mAccu ); mStepNo = 0; break; default: assert( 0 ); } // and write: *dcursor++ = binToHex( value ); } return mStepNo == 0; } } // namespace KMime diff --git a/kmime/kmime_codec_qp.h b/kmime/kmime_codec_qp.h index f419c18e2..b1109d864 100644 --- a/kmime/kmime_codec_qp.h +++ b/kmime/kmime_codec_qp.h @@ -1,228 +1,228 @@ /* -*- c++ -*- kmime_codec_qp.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 API for handling @ref MIME data and defines the @ref QuotedPrintable, @ref RFC2047Q, and @ref RFC2231 @ref Codec classes. @brief Defines the classes QuotedPrintableCodec, Rfc2047QEncodingCodec, and Rfc2231EncodingCodec. @authors Marc Mutz \ @glossary @anchor QuotedPrintable @anchor quotedprintable @b quoted-printable: a binary to text encoding scheme based on Section 6.7 of @ref RFC2045. @glossary @anchor RFC2047Q @anchor rfc2047q @b RFC @b 2047Q: Section 4.2 of @ref RFC2047. @glossary @anchor RFC2231 @anchor rfc2231 @b RFC @b 2231: RFC that defines the MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations. */ #ifndef __KMIME_CODEC_QP__ #define __KMIME_CODEC_QP__ #include "kmime_codecs.h" namespace KMime { /** @brief A class representing the @ref codec for @ref QuotedPrintable as specified in @ref RFC2045 (section 6.7). */ class KMIME_EXPORT QuotedPrintableCodec : public Codec { protected: friend class Codec; /** Constructs a QuotedPrintable codec. */ QuotedPrintableCodec() : Codec() {} public: /** Destroys the codec. */ virtual ~QuotedPrintableCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "quoted-printable"; } /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { // all chars encoded: int result = 3*insize; // then after 25 hexchars comes a soft linebreak: =(\r)\n result += (withCRLF ? 3 : 2) * (insize / 25); return result; } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const; /** @copydoc Codec::makeEncoder() */ Encoder *makeEncoder( bool withCRLF=false ) const; /** @copydoc Codec::makeDecoder() */ Decoder *makeDecoder( bool withCRLF=false ) const; }; /** @brief A class representing the @ref codec for the Q encoding as specified in @ref RFC2047Q. */ class KMIME_EXPORT Rfc2047QEncodingCodec : public Codec { protected: friend class Codec; /** Constructs a RFC2047Q codec. */ Rfc2047QEncodingCodec() : Codec() {} public: /** Destroys the codec. */ virtual ~Rfc2047QEncodingCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "q"; } /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { Q_UNUSED( withCRLF ); // this one is simple: We don't do linebreaking, so all that can // happen is that every char needs encoding, so: return 3 * insize; } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const; /** @copydoc Codec::makeEncoder() */ Encoder *makeEncoder( bool withCRLF=false ) const; /** @copydoc Codec::makeDecoder() */ Decoder *makeDecoder( bool withCRLF=false ) const; }; /** @brief A class representing the @ref codec for @ref RFC2231. */ class KMIME_EXPORT Rfc2231EncodingCodec : public Codec { protected: friend class Codec; /** Constructs a RFC2231 codec. */ Rfc2231EncodingCodec() : Codec() {} public: /** Destroys the codec. */ virtual ~Rfc2231EncodingCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "x-kmime-rfc2231"; } /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { Q_UNUSED( withCRLF ); // same as for "q" encoding: return 3 * insize; } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const; /** @copydoc Codec::makeEncoder() */ Encoder *makeEncoder( bool withCRLF=false ) const; /** @copydoc Codec::makeDecoder() */ Decoder *makeDecoder( bool withCRLF=false ) const; }; } // namespace KMime #endif // __KMIME_CODEC_QP__ diff --git a/kmime/kmime_codec_uuencode.cpp b/kmime/kmime_codec_uuencode.cpp index e7fdcbd55..0b8461048 100644 --- a/kmime/kmime_codec_uuencode.cpp +++ b/kmime/kmime_codec_uuencode.cpp @@ -1,244 +1,244 @@ /* -*- c++ -*- kmime_codec_uuencode.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2002 Marc Mutz 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 API for handling @ref MIME data and defines a @ref uuencode @ref Codec class. @brief Defines the UUCodec class. @authors Marc Mutz \ */ #include "kmime_codec_uuencode.h" #include #include using namespace KMime; namespace KMime { class UUDecoder : public Decoder { uint mStepNo; uchar mAnnouncedOctetCount; // (on current line) uchar mCurrentOctetCount; // (on current line) uchar mOutbits; bool mLastWasCRLF : 1; bool mSawBegin : 1; // whether we already saw ^begin... uint mIntoBeginLine : 3; // count #chars we compared against "begin" 0..5 bool mSawEnd : 1; // whether we already saw ^end... uint mIntoEndLine : 2; // count #chars we compared against "end" 0..3 void searchForBegin( const char* &scursor, const char * const send ); protected: friend class UUCodec; UUDecoder( bool withCRLF=false ) : Decoder( withCRLF ), mStepNo( 0 ), mAnnouncedOctetCount( 0 ), mCurrentOctetCount( 0 ), mOutbits( 0 ), mLastWasCRLF( true ), mSawBegin( false ), mIntoBeginLine( 0 ), mSawEnd( false ), mIntoEndLine( 0 ) {} public: virtual ~UUDecoder() {} bool decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ); // ### really needs no finishing??? bool finish( char* &dcursor, const char * const dend ) { Q_UNUSED( dcursor ); Q_UNUSED( dend ); return true; } }; Encoder * UUCodec::makeEncoder( bool ) const { return 0; // encoding not supported } Decoder * UUCodec::makeDecoder( bool withCRLF ) const { return new UUDecoder( withCRLF ); } /********************************************************/ /********************************************************/ /********************************************************/ void UUDecoder::searchForBegin( const char* &scursor, const char * const send ) { static const char begin[] = "begin\n"; static const uint beginLength = 5; // sic! assert( !mSawBegin || mIntoBeginLine > 0 ); while ( scursor != send ) { uchar ch = *scursor++; if ( ch == begin[mIntoBeginLine] ) { if ( mIntoBeginLine < beginLength ) { // found another char ++mIntoBeginLine; if ( mIntoBeginLine == beginLength ) { mSawBegin = true; // "begin" complete, now search the next \n... } } else { // mIntoBeginLine == beginLength // found '\n': begin line complete mLastWasCRLF = true; mIntoBeginLine = 0; return; } } else if ( mSawBegin ) { // OK, skip stuff until the next \n } else { kWarning() << "UUDecoder: garbage before \"begin\", resetting parser"; mIntoBeginLine = 0; } } } // uuencoding just shifts all 6-bit octets by 32 (SP/' '), except NUL, // which gets mapped to 0x60 static inline uchar uuDecode( uchar c ) { return ( c - ' ' ) // undo shift and & 0x3F; // map 0x40 (0x60-' ') to 0... } bool UUDecoder::decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) { // First, check whether we still need to find the "begin" line: if ( !mSawBegin || mIntoBeginLine != 0 ) { searchForBegin( scursor, send ); } else if ( mSawEnd ) { // or if we are past the end line: scursor = send; // do nothing anymore... return true; } while ( dcursor != dend && scursor != send ) { uchar ch = *scursor++; uchar value; // Check whether we need to look for the "end" line: if ( mIntoEndLine > 0 ) { static const char end[] = "end"; static const uint endLength = 3; if ( ch == end[mIntoEndLine] ) { ++mIntoEndLine; if ( mIntoEndLine == endLength ) { mSawEnd = true; scursor = send; // shortcut to the end return true; } continue; } else { kWarning() << "UUDecoder: invalid line octet count looks like \"end\" (mIntoEndLine =" << mIntoEndLine << ")!"; mIntoEndLine = 0; // fall through... } } // Normal parsing: // The first char of a line is an encoding of the length of the // current line. We simply ignore it: if ( mLastWasCRLF ) { // reset char-per-line counter: mLastWasCRLF = false; mCurrentOctetCount = 0; // try to decode the chars-on-this-line announcement: if ( ch == 'e' ) { // maybe the beginning of the "end"? ;-) mIntoEndLine = 1; } else if ( ch > 0x60 ) { // ### invalid line length char: what shall we do?? } else if ( ch > ' ' ) { mAnnouncedOctetCount = uuDecode( ch ); } else if ( ch == '\n' ) { mLastWasCRLF = true; // oops, empty line } continue; } // try converting ch to a 6-bit value: if ( ch > 0x60 ) { continue; // invalid char } else if ( ch > ' ' ) { value = uuDecode( ch ); } else if ( ch == '\n' ) { // line end mLastWasCRLF = true; continue; } else { continue; } // add the new bits to the output stream and flush full octets: switch ( mStepNo ) { case 0: mOutbits = value << 2; break; case 1: if ( mCurrentOctetCount < mAnnouncedOctetCount ) { *dcursor++ = (char)(mOutbits | value >> 4); } ++mCurrentOctetCount; mOutbits = value << 4; break; case 2: if ( mCurrentOctetCount < mAnnouncedOctetCount ) { *dcursor++ = (char)(mOutbits | value >> 2); } ++mCurrentOctetCount; mOutbits = value << 6; break; case 3: if ( mCurrentOctetCount < mAnnouncedOctetCount ) { *dcursor++ = (char)(mOutbits | value); } ++mCurrentOctetCount; mOutbits = 0; break; default: assert( 0 ); } mStepNo = (mStepNo + 1) % 4; // check whether we ran over the announced octet count for this line: kWarning( mCurrentOctetCount == mAnnouncedOctetCount + 1 ) << "UUDecoder: mismatch between announced (" << mAnnouncedOctetCount << ") and actual line octet count!"; } // return false when caller should call us again: return scursor == send; } // UUDecoder::decode() } // namespace KMime diff --git a/kmime/kmime_codec_uuencode.h b/kmime/kmime_codec_uuencode.h index 112fc09a9..2586f9127 100644 --- a/kmime/kmime_codec_uuencode.h +++ b/kmime/kmime_codec_uuencode.h @@ -1,114 +1,114 @@ /* -*- c++ -*- kmime_codec_uuencode.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2002 Marc Mutz 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 API for handling @ref MIME data and defines a @ref uuencode @ref Codec class. @brief Defines the UUCodec class. @authors Marc Mutz \ @glossary @anchor UUEncode @anchor uuencode @b uuencode: a binary to text encoding scheme. For more information, see the Wikipedia Uuencode page. */ #ifndef __KMIME_CODEC_UUENCODE_H__ #define __KMIME_CODEC_UUENCODE_H__ #include "kmime_codecs.h" namespace KMime { /** @brief A class representing the @ref UUEncode @ref codec. */ class KMIME_EXPORT UUCodec : public Codec { protected: friend class Codec; /** Constructs a UUEncode codec. */ UUCodec() : Codec() {} public: /** Destroys the codec. */ virtual ~UUCodec() {} /** @copydoc Codec::name() */ const char *name() const { return "x-uuencode"; } /** @copydoc Codec::maxEncodedSizeFor() */ int maxEncodedSizeFor( int insize, bool withCRLF=false ) const { (void)withCRLF; return insize; // we have no encoder! } /** @copydoc Codec::maxDecodedSizeFor() */ int maxDecodedSizeFor( int insize, bool withCRLF=false ) const { // assuming all characters are part of the uuencode stream (which // does almost never hold due to required linebreaking; but // additional non-uu chars don't affect the output size), each // 4-tupel of them becomes a 3-tupel in the decoded octet // stream. So: int result = ( ( insize + 3 ) / 4 ) * 3; // but all of them may be \n, so if ( withCRLF ) { result *= 2; // :-o } return result; } /** @copydoc Codec::makeEncoder() */ Encoder *makeEncoder( bool withCRLF=false ) const; /** @copydoc Codec::makeEncoder() */ Decoder *makeDecoder( bool withCRLF=false ) const; }; } // namespace KMime #endif // __KMIME_CODEC_UUENCODE_H__ diff --git a/kmime/kmime_codecs.cpp b/kmime/kmime_codecs.cpp index 5f1b78536..6b0f9a824 100644 --- a/kmime/kmime_codecs.cpp +++ b/kmime/kmime_codecs.cpp @@ -1,234 +1,234 @@ /* -*- c++ -*- - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 API for handling MIME data and defines the Codec class. @brief Defines the Codec class. @authors Marc Mutz \ */ #include "kmime_codecs.h" #include "kmime_util.h" #include "kmime_codec_base64.h" #include "kmime_codec_qp.h" #include "kmime_codec_uuencode.h" #include "kmime_codec_identity.h" #include "kautodeletehash.h" #include #include #include #include #include #include #include #include using namespace KMime; namespace KMime { // global list of KMime::Codec's //@cond PRIVATE KAutoDeleteHash *Codec::all = 0; K_GLOBAL_STATIC(QMutex, dictLock) //@endcond void Codec::cleanupCodec() { delete all; all = 0; } void Codec::fillDictionary() { //all->insert( "7bit", new SevenBitCodec() ); //all->insert( "8bit", new EightBitCodec() ); all->insert( "base64", new Base64Codec() ); all->insert( "quoted-printable", new QuotedPrintableCodec() ); all->insert( "b", new Rfc2047BEncodingCodec() ); all->insert( "q", new Rfc2047QEncodingCodec() ); all->insert( "x-kmime-rfc2231", new Rfc2231EncodingCodec() ); all->insert( "x-uuencode", new UUCodec() ); //all->insert( "binary", new BinaryCodec() ); } Codec *Codec::codecForName( const char *name ) { const QByteArray ba( name ); return codecForName( ba ); } Codec *Codec::codecForName( const QByteArray &name ) { dictLock->lock(); // protect "all" if ( !all ) { all = new KAutoDeleteHash(); qAddPostRoutine(cleanupCodec); fillDictionary(); } QByteArray lowerName = name; kAsciiToLower( lowerName.data() ); Codec *codec = (*all)[ lowerName ]; // FIXME: operator[] adds an entry into the hash dictLock->unlock(); if ( !codec ) { kDebug(5320) << "Unknown codec \"" << name << "\" requested!"; } return codec; } bool Codec::encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend, bool withCRLF ) const { // get an encoder: Encoder *enc = makeEncoder( withCRLF ); assert( enc ); // encode and check for output buffer overflow: while ( !enc->encode( scursor, send, dcursor, dend ) ) { if ( dcursor == dend ) { delete enc; return false; // not enough space in output buffer } } // finish and check for output buffer overflow: while ( !enc->finish( dcursor, dend ) ) { if ( dcursor == dend ) { delete enc; return false; // not enough space in output buffer } } // cleanup and return: delete enc; return true; // successfully encoded. } QByteArray Codec::encode( const QByteArray &src, bool withCRLF ) const { // allocate buffer for the worst case: QByteArray result; result.resize( maxEncodedSizeFor( src.size(), withCRLF ) ); // set up iterators: QByteArray::ConstIterator iit = src.begin(); QByteArray::ConstIterator iend = src.end(); QByteArray::Iterator oit = result.begin(); QByteArray::ConstIterator oend = result.end(); // encode if ( !encode( iit, iend, oit, oend, withCRLF ) ) { kFatal() << name() << "codec lies about it's mEncodedSizeFor()"; } // shrink result to actual size: result.truncate( oit - result.begin() ); return result; } QByteArray Codec::decode( const QByteArray &src, bool withCRLF ) const { // allocate buffer for the worst case: QByteArray result; result.resize( maxDecodedSizeFor( src.size(), withCRLF ) ); // set up iterators: QByteArray::ConstIterator iit = src.begin(); QByteArray::ConstIterator iend = src.end(); QByteArray::Iterator oit = result.begin(); QByteArray::ConstIterator oend = result.end(); // decode if ( !decode( iit, iend, oit, oend, withCRLF ) ) { kFatal() << name() << "codec lies about it's maxDecodedSizeFor()"; } // shrink result to actual size: result.truncate( oit - result.begin() ); return result; } bool Codec::decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend, bool withCRLF ) const { // get a decoder: Decoder *dec = makeDecoder( withCRLF ); assert( dec ); // decode and check for output buffer overflow: while ( !dec->decode( scursor, send, dcursor, dend ) ) { if ( dcursor == dend ) { delete dec; return false; // not enough space in output buffer } } // finish and check for output buffer overflow: while ( !dec->finish( dcursor, dend ) ) { if ( dcursor == dend ) { delete dec; return false; // not enough space in output buffer } } // cleanup and return: delete dec; return true; // successfully encoded. } // write as much as possible off the output buffer. Return true if // flushing was complete, false if some chars could not be flushed. bool Encoder::flushOutputBuffer( char* &dcursor, const char * const dend ) { int i; // copy output buffer to output stream: for ( i = 0 ; dcursor != dend && i < mOutputBufferCursor ; ++i ) { *dcursor++ = mOutputBuffer[i]; } // calculate the number of missing chars: int numCharsLeft = mOutputBufferCursor - i; // push the remaining chars to the begin of the buffer: if ( numCharsLeft ) { ::memmove( mOutputBuffer, mOutputBuffer + i, numCharsLeft ); } // adjust cursor: mOutputBufferCursor = numCharsLeft; return !numCharsLeft; } } // namespace KMime diff --git a/kmime/kmime_codecs.h b/kmime/kmime_codecs.h index dca9e4887..c4cb7cef6 100644 --- a/kmime/kmime_codecs.h +++ b/kmime/kmime_codecs.h @@ -1,511 +1,511 @@ /* -*- c++ -*- - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 API for handling @ref MIME data and defines the Codec class. @brief Defines the classes Codec class. @authors Marc Mutz \ @glossary @anchor MIME @anchor mime @b MIME: Multipurpose Internet Mail Extensions or @acronym MIME is an Internet Standard that extends the format of e-mail to support text in character sets other than US-ASCII, non-text attachments, multi-part message bodies, and header information in non-ASCII character sets. Virtually all human-written Internet e-mail and a fairly large proportion of automated e-mail is transmitted via @acronym SMTP in MIME format. Internet e-mail is so closely associated with the SMTP and MIME standards that it is sometimes called SMTP/MIME e-mail. The content types defined by MIME standards are also of growing importance outside of e-mail, such as in communication protocols like @acronym HTTP for the World Wide Web. MIME is also a fundamental component of communication protocols such as HTTP, which requires that data be transmitted in the context of e-mail-like messages, even though the data may not actually be e-mail. @glossary @anchor codec @anchor codecs @anchor Codec @anchor Codecs @b codec: a program capable of performing encoding and decoding on a digital data stream. Codecs encode data for storage or encryption and decode it for viewing or editing. @glossary @anchor CRLF @b CRLF: a "Carriage Return (0x0D)" followed by a "Line Feed (0x0A)", two ASCII control characters used to represent a newline on some operating systems, notably DOS and Microsoft Windows. @glossary @anchor LF @b LF: a "Line Feed (0x0A)" ASCII control character used to represent a newline on some operating systems, notably Unix, Unix-like, and Linux. */ #ifndef __KMIME_CODECS__ #define __KMIME_CODECS__ #include #include // for kFatal() #include "kmime_export.h" namespace KMime { template class KAutoDeleteHash; class Encoder; class Decoder; /** @brief An abstract base class of @ref codecs for common mail transfer encodings. Provides an abstract base class of @ref codecs like base64 and quoted-printable. Implemented as a singleton. */ class KMIME_EXPORT Codec { protected: //@cond PRIVATE static KAutoDeleteHash *all; static void cleanupCodec(); //@endcond /** Contructs the codec. */ Codec() {} public: /** Returns a codec associated with the specified @p name. @param name points to a character string containing a valid codec name. */ static Codec *codecForName( const char *name ); /** Returns a codec associated with the specified @p name. @param name is a QByteArray containing a valid codec name. */ static Codec *codecForName( const QByteArray &name ); /** Computes the maximum size, in characters, needed for the encoding. @param insize is the number of input characters to be encoded. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. @return the maximum number of characters in the encoding. */ virtual int maxEncodedSizeFor( int insize, bool withCRLF=false ) const = 0; /** Computes the maximum size, in characters, needed for the deccoding. @param insize is the number of input characters to be decoded. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. @return the maximum number of characters in the decoding. */ virtual int maxDecodedSizeFor( int insize, bool withCRLF=false ) const = 0; /** Creates the encoder for the codec. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. @return a pointer to an instance of the codec's encoder. */ virtual Encoder *makeEncoder( bool withCRLF=false ) const = 0; /** Creates the decoder for the codec. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. @return a pointer to an instance of the codec's decoder. */ virtual Decoder *makeDecoder( bool withCRLF=false ) const = 0; /** Convenience wrapper that can be used for small chunks of data when you can provide a large enough buffer. The default implementation creates an Encoder and uses it. Encodes a chunk of bytes starting at @p scursor and extending to @p send into the buffer described by @p dcursor and @p dend. This function doesn't support chaining of blocks. The returned block cannot be added to, but you don't need to finalize it, too. Example usage (@p in contains the input data):
             KMime::Codec *codec = KMime::Codec::codecForName( "base64" );
             kFatal( !codec ) << "no base64 codec found!?";
             QByteArray out( in.size()*1.4 ); // crude maximal size of b64 encoding
             QByteArray::Iterator iit = in.begin();
             QByteArray::Iterator oit = out.begin();
             if ( !codec->encode( iit, in.end(), oit, out.end() ) ) {
               kDebug(5320) << "output buffer too small";
               return;
             }
             kDebug(5320) << "Size of encoded data:" << oit - out.begin();
             
      @param scursor is a pointer to the start of the input buffer. @param send is a pointer to the end of the input buffer. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. @return false if the encoded data didn't fit into the output buffer; true otherwise. */ virtual bool encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend, bool withCRLF=false ) const; /** Convenience wrapper that can be used for small chunks of data when you can provide a large enough buffer. The default implementation creates a Decoder and uses it. Decodes a chunk of bytes starting at @p scursor and extending to @p send into the buffer described by @p dcursor and @p dend. This function doesn't support chaining of blocks. The returned block cannot be added to, but you don't need to finalize it, too. Example usage (@p in contains the input data):
             KMime::Codec *codec = KMime::Codec::codecForName( "base64" );
             kFatal( !codec ) << "no base64 codec found!?";
             QByteArray out( in.size() ); // good guess for any encoding...
             QByteArray::Iterator iit = in.begin();
             QByteArray::Iterator oit = out.begin();
             if ( !codec->decode( iit, in.end(), oit, out.end() ) ) {
               kDebug(5320) << "output buffer too small";
               return;
             }
             kDebug(5320) << "Size of decoded data:" << oit - out.begin();
             
      @param scursor is a pointer to the start of the input buffer. @param send is a pointer to the end of the input buffer. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. @return false if the decoded data didn't fit into the output buffer; true otherwise. */ virtual bool decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend, bool withCRLF=false ) const; /** Even more convenient, but also a bit slower and more memory intensive, since it allocates storage for the worst case and then shrinks the result QByteArray to the actual size again. For use with small @p src. @param src is a QByteArray containing the data to encode. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. */ virtual QByteArray encode( const QByteArray &src, bool withCRLF=false ) const; /** Even more convenient, but also a bit slower and more memory intensive, since it allocates storage for the worst case and then shrinks the result QByteArray to the actual size again. For use with small @p src. @param src is a QByteArray containing the data to decode. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. */ virtual QByteArray decode( const QByteArray &src, bool withCRLF=false ) const; /** Returns the name of the encoding. Guaranteed to be lowercase. */ virtual const char *name() const = 0; /** Destroys the codec. */ virtual ~Codec() {} private: /** Fills the KAutoDeleteHash with all the supported codecs. */ static void fillDictionary(); }; /** @brief Stateful CTE decoder class Stateful decoder class, modelled after QTextDecoder. @section Overview KMime decoders are designed to be able to process encoded data in chunks of arbitrary size and to work with output buffers of also arbitrary size. They maintain any state necessary to go on where the previous call left off. The class consists of only two methods of interest: see decode, which decodes an input block and finalize, which flushes any remaining data to the output stream. Typically, you will create a decoder instance, call decode as often as necessary, then call finalize (most often a single call suffices, but it might be that during that call the output buffer is filled, so you should be prepared to call finalize as often as necessary, ie. until it returns @p true). @section Return Values Both methods return @p true to indicate that they've finished their job. For decode, a return value of @p true means that the current input block has been finished (@p false most often means that the output buffer is full, but that isn't required behavior. The decode call is free to return at arbitrary times during processing). For finalize, a return value of @p true means that all data implicitly or explicitly stored in the decoder instance has been flushed to the output buffer. A @p false return value should be interpreted as "check if the output buffer is full and call me again", just as with decode. @section Usage Pattern Since the decoder maintains state, you can only use it once. After a sequence of input blocks has been processed, you finalize the output and then delete the decoder instance. If you want to process another input block sequence, you create a new instance. Typical usage (@p in contains the (base64-encoded) input data), taking into account all the conventions detailed above:
         KMime::Codec *codec = KMime::Codec::codecForName( "base64" );
         kFatal( !codec ) << "No codec found for base64!";
         KMime::Decoder *dec = codec->makeDecoder();
         assert( dec ); // should not happen
         QByteArray out( 256 ); // small buffer is enough ;-)
         QByteArray::Iterator iit = in.begin();
         QByteArray::Iterator oit = out.begin();
         // decode the chunk
         while ( !dec->decode( iit, in.end(), oit, out.end() ) )
           if ( oit == out.end() ) { // output buffer full, process contents
             do_something_with( out );
             oit = out.begin();
           }
         // repeat while loop for each input block
         // ...
         // finish (flush remaining data from decoder):
         while ( !dec->finish( oit, out.end() ) )
           if ( oit == out.end() ) { // output buffer full, process contents
             do_something_with( out );
             oit = out.begin();
           }
         // now process last chunk:
         out.resize( oit - out.begin() );
         do_something_with( out );
         // _delete_ the decoder, but not the codec:
         delete dec;
         
      */ class Decoder { protected: friend class Codec; /** Protected constructor. Use KMime::Codec::makeDecoder to create an instance. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. */ Decoder( bool withCRLF=false ) : mWithCRLF( withCRLF ) {} public: /** Destroys the decoder. */ virtual ~Decoder() {} /** Decodes a chunk of data, maintaining state information between calls. See class decumentation for calling conventions. @param scursor is a pointer to the start of the input buffer. @param send is a pointer to the end of the input buffer. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. */ virtual bool decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) = 0; /** Call this method to finalize the output stream. Writes all remaining data and resets the decoder. See KMime::Codec for calling conventions. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. */ virtual bool finish( char* &dcursor, const char * const dend ) = 0; protected: //@cond PRIVATE const bool mWithCRLF; //@endcond }; /** @brief Stateful encoder class. Stateful encoder class, modeled after QTextEncoder. */ class Encoder { protected: friend class Codec; /** Protected constructor. Use KMime::Codec::makeEncoder if you want one. @param withCRLF if true, make the newlines @ref CRLF; else use @ref LF. */ explicit Encoder( bool withCRLF=false ) : mOutputBufferCursor( 0 ), mWithCRLF( withCRLF ) {} public: /** Destroys the encoder. */ virtual ~Encoder() {} /** Encodes a chunk of data, maintaining state information between calls. See KMime::Codec for calling conventions. @param scursor is a pointer to the start of the input buffer. @param send is a pointer to the end of the input buffer. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. */ virtual bool encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend ) = 0; /** Call this method to finalize the output stream. Writes all remaining data and resets the encoder. See KMime::Codec for calling conventions. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. */ virtual bool finish( char* &dcursor, const char * const dend ) = 0; protected: /** The maximum number of characters permitted in the output buffer. */ enum { maxBufferedChars = 8 /**< Eight */ }; /** Writes character @p ch to the output stream or the output buffer, depending on whether or not the output stream has space left. @param ch is the character to write. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. @return true if written to the output stream; else false if buffered. */ bool write( char ch, char* &dcursor, const char * const dend ) { if ( dcursor != dend ) { // if there's space in the output stream, write there: *dcursor++ = ch; return true; } else { // else buffer the output: kFatal( mOutputBufferCursor >= maxBufferedChars ) << "KMime::Encoder: internal buffer overflow!"; mOutputBuffer[ mOutputBufferCursor++ ] = ch; return false; } } /** Writes characters from the output buffer to the output stream. Implementations of encode and finish should call this at the very beginning and for each iteration of the while loop. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. @return true if all chars could be written, false otherwise */ bool flushOutputBuffer( char* &dcursor, const char * const dend ); /** Convenience function. Outputs @ref LF or @ref CRLF, based on the state of mWithCRLF. @param dcursor is a pointer to the start of the output buffer. @param dend is a pointer to the end of the output buffer. */ bool writeCRLF( char* &dcursor, const char * const dend ) { if ( mWithCRLF ) { write( '\r', dcursor, dend ); } return write( '\n', dcursor, dend ); } private: /** An output buffer to simplify some codecs. Used with write() and flushOutputBuffer(). */ //@cond PRIVATE char mOutputBuffer[ maxBufferedChars ]; //@endcond protected: //@cond PRIVATE uchar mOutputBufferCursor; const bool mWithCRLF; //@endcond }; } // namespace KMime #endif // __KMIME_CODECS__ diff --git a/kmime/kmime_content.cpp b/kmime/kmime_content.cpp index 3731ed384..47d3c0e52 100644 --- a/kmime/kmime_content.cpp +++ b/kmime/kmime_content.cpp @@ -1,1049 +1,1049 @@ /* kmime_content.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause Copyright (c) 2009 Constantin Berzan 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 API for handling @ref MIME data and defines the Content class. @brief Defines the Content class. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #include "kmime_content.h" #include "kmime_content_p.h" #include "kmime_header_parsing.h" #include "kmime_parsers.h" #include "kmime_util_p.h" #include #include #include #include #include #include #include #include using namespace KMime; namespace KMime { Content::Content() : d_ptr( new ContentPrivate( this ) ) { } Content::Content( Content *parent ) : d_ptr( new ContentPrivate( this ) ) { d_ptr->parent = parent; } Content::Content( const QByteArray &h, const QByteArray &b ) : d_ptr( new ContentPrivate( this ) ) { d_ptr->head = h; d_ptr->body = b; } Content::Content( const QByteArray &h, const QByteArray &b, Content *parent ) : d_ptr( new ContentPrivate( this ) ) { d_ptr->head = h; d_ptr->body = b; d_ptr->parent = parent; } Content::Content( ContentPrivate *d ) : d_ptr( d ) { } Content::~Content() { qDeleteAll( h_eaders ); h_eaders.clear(); delete d_ptr; d_ptr = 0; } bool Content::hasContent() const { return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents.isEmpty(); } void Content::setContent( const QList &l ) { Q_D(Content); //qDebug("Content::setContent( const QList &l ) : start"); d->head.clear(); d->body.clear(); //usage of textstreams is much faster than simply appending the strings QTextStream hts( &( d->head ), QIODevice::WriteOnly ); QTextStream bts( &( d->body ), QIODevice::WriteOnly ); hts.setCodec( "ISO 8859-1" ); bts.setCodec( "ISO 8859-1" ); bool isHead = true; foreach ( const QByteArray& line, l ) { if ( isHead && line.isEmpty() ) { isHead = false; continue; } if ( isHead ) { hts << line << "\n"; } else { bts << line << "\n"; } } //qDebug("Content::setContent( const QList & l ) : finished"); } void Content::setContent( const QByteArray &s ) { Q_D(Content); d->head.clear(); d->body.clear(); // empty header if ( s.startsWith( '\n' ) ) { d->body = s.right( s.length() - 1 ); return; } int pos = s.indexOf( "\n\n", 0 ); if ( pos > -1 ) { d->head = s.left( ++pos ); //header *must* end with "\n" !! d->body = s.mid( pos + 1, s.length() - pos - 1 ); } else { d->head = s; } } QByteArray Content::head() const { return d_ptr->head; } void Content::setHead( const QByteArray &head ) { d_ptr->head = head; if ( !head.endsWith( '\n' ) ) d_ptr->head += '\n'; } QByteArray Content::body() const { return d_ptr->body; } void Content::setBody( const QByteArray &body ) { d_ptr->body = body; } void Content::parse() { Q_D( Content ); // Clean up old headers and parse them again. qDeleteAll( h_eaders ); h_eaders = HeaderParsing::parseHeaders( d->head ); foreach( Headers::Base *h, h_eaders ) { h->setParent( this ); } // If we are frozen, save the body as-is. This is done because parsing // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.) if( d->frozen ) { d->frozenBody = d->body; } // Clean up old sub-Contents and parse them again. qDeleteAll( d->contents ); d->contents.clear(); Headers::ContentType *ct = contentType(); if( ct->isText() ) { // This content is either text, or of unknown type. if( d->parseUuencoded() ) { // This is actually uuencoded content generated by broken software. } else if( d->parseYenc() ) { // This is actually yenc content generated by broken software. } else { // This is just plain text. } } else if( ct->isMultipart() ) { // This content claims to be MIME multipart. if( d->parseMultipart() ) { // This is actual MIME multipart content. } else { // Parsing failed; treat this content as "text/plain". kWarning() << "Failed to parse multipart. Treating as text/plain."; ct->setMimeType( "text/plain" ); ct->setCharset( "US-ASCII" ); } } else { // This content is something else, like an image or something... } } bool Content::isFrozen() const { return d_ptr->frozen; } void Content::setFrozen( bool frozen ) { d_ptr->frozen = frozen; } void Content::assemble() { Q_D( Content ); if( d->frozen ) { return; } d->head = assembleHeaders(); foreach( Content *c, contents() ) { c->assemble(); } } QByteArray Content::assembleHeaders() { QByteArray newHead; foreach( const Headers::Base *h, h_eaders ) { if( !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; } } return newHead; } void Content::clear() { Q_D(Content); qDeleteAll( h_eaders ); h_eaders.clear(); clearContents(); d->head.clear(); d->body.clear(); } void Content::clearContents( bool del ) { Q_D(Content); if( del ) { qDeleteAll( d->contents ); } d->contents.clear(); } QByteArray Content::encodedContent( bool useCrLf ) { Q_D(Content); QByteArray e; // Head. e = d->head; e += '\n'; // Body. if( d->frozen ) { // This Content is frozen. if( d->frozenBody.isEmpty() ) { // This Content has never been parsed. e += d->body; } else { // Use the body as it was before parsing. e += d->frozenBody; } } else if( !d->body.isEmpty() ) { // This is a single-part Content. Headers::ContentTransferEncoding *enc = contentTransferEncoding(); Q_ASSERT( enc->encoding() != Headers::CEuuenc ); Q_ASSERT( enc->encoding() != Headers::CEbinary ); if (enc->needToEncode()) { if ( enc->encoding() == Headers::CEquPr ) { e += KCodecs::quotedPrintableEncode( d->body, false ); } else { e += KCodecs::base64Encode( d->body, true ); e += '\n'; } } else { e += d->body; } } else if ( !d->contents.isEmpty() ) { // This is a multipart Content. Headers::ContentType *ct=contentType(); QByteArray boundary = "\n--" + ct->boundary(); //add all (encoded) contents separated by boundaries foreach ( Content *c, d->contents ) { e+=boundary + '\n'; e += c->encodedContent( false ); // don't convert LFs here, we do that later!!!!! } //finally append the closing boundary e += boundary+"--\n"; }; if ( useCrLf ) { return LFtoCRLF( e ); } else { return e; } } QByteArray Content::decodedContent() { QByteArray temp, ret; Headers::ContentTransferEncoding *ec=contentTransferEncoding(); bool removeTrailingNewline=false; int size = d_ptr->body.length(); if ( size == 0 ) { return ret; } temp.resize( size ); memcpy( temp.data(), d_ptr->body.data(), size ); if ( ec->decoded() ) { ret = temp; removeTrailingNewline = true; } else { switch( ec->encoding() ) { case Headers::CEbase64 : KCodecs::base64Decode( temp, ret ); break; case Headers::CEquPr : ret = KCodecs::quotedPrintableDecode( d_ptr->body ); ret.resize( ret.size() - 1 ); // remove null-char removeTrailingNewline = true; break; case Headers::CEuuenc : KCodecs::uudecode( temp, ret ); break; case Headers::CEbinary : ret = temp; removeTrailingNewline = false; break; default : ret = temp; removeTrailingNewline = true; } } if ( removeTrailingNewline && ( ret.size() > 0 ) && ( ret[ret.size()-1] == '\n') ) { ret.resize( ret.size() - 1 ); } return ret; } QString Content::decodedText( bool trimText, bool removeTrailingNewlines ) { if ( !decodeText() ) { //this is not a text content !! return QString(); } bool ok = true; QTextCodec *codec = KGlobal::charsets()->codecForName( contentType()->charset(), ok ); QString s = codec->toUnicode( d_ptr->body.data(), d_ptr->body.length() ); if ( trimText && removeTrailingNewlines ) { int i; for ( i = s.length() - 1; i >= 0; --i ) { if ( !s[i].isSpace() ) { break; } } s.truncate( i + 1 ); } else { if ( s.right( 1 ) == "\n" ) { s.truncate( s.length() - 1 ); // remove trailing new-line } } return s; } void Content::fromUnicodeString( const QString &s ) { bool ok = true; QTextCodec *codec = KGlobal::charsets()->codecForName( contentType()->charset(), ok ); if ( !ok ) { // no suitable codec found => try local settings and hope the best ;-) codec = KGlobal::locale()->codecForEncoding(); QByteArray chset = KGlobal::locale()->encoding(); contentType()->setCharset( chset ); } d_ptr->body = codec->fromUnicode( s ); contentTransferEncoding()->setDecoded( true ); //text is always decoded } Content *Content::textContent() { Content *ret=0; //return the first content with mimetype=text/* if ( contentType()->isText() ) { ret = this; } else { foreach ( Content *c, d_ptr->contents ) { if ( ( ret = c->textContent() ) != 0 ) { break; } } } return ret; } Content::List Content::attachments( bool incAlternatives ) { List attachments; if ( d_ptr->contents.isEmpty() ) { attachments.append( this ); } else { foreach ( Content *c, d_ptr->contents ) { if ( !incAlternatives && c->contentType()->category() == Headers::CCalternativePart ) { continue; } else { attachments += c->attachments( incAlternatives ); } } } if ( isTopLevel() ) { Content *text = textContent(); if ( text ) { attachments.removeAll( text ); } } return attachments; } Content::List Content::contents() const { return d_ptr->contents; } void Content::addContent( Content *c, bool prepend ) { Q_D( Content ); // If this message is single-part; make it multipart first. if( d->contents.isEmpty() && !contentType()->isMultipart() ) { // The current body will be our first sub-Content. Content *main = new Content( this ); // Move the MIME headers to the newly created sub-Content. // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers // are not moved to the subcontent; they remain with the top-level content. for ( Headers::Base::List::iterator it = h_eaders.begin(); it != h_eaders.end(); ) { if ( (*it)->isMimeHeader() ) { // Add to new content. main->setHeader( *it ); // Remove from this content. it = h_eaders.erase( it ); } else { ++it; } } // Adjust the Content-Type of the newly created sub-Content. main->contentType()->setCategory( Headers::CCmixedPart ); // Move the body to the new subcontent. main->setBody( d->body ); d->body.clear(); // Add the subcontent. d->contents.append( main ); // Convert this content to "multipart/mixed". Headers::ContentType *ct = contentType(); ct->setMimeType( "multipart/mixed" ); ct->setBoundary( multiPartBoundary() ); ct->setCategory( Headers::CCcontainer ); contentTransferEncoding()->clear(); // 7Bit, decoded. } // Add the new content. if( prepend ) { d->contents.prepend( c ); } else { d->contents.append( c ); } if( c->parent() != this ) { // If the content was part of something else, this will remove it from there. c->setParent( this ); } } void Content::removeContent( Content *c, bool del ) { Q_D( Content ); Q_ASSERT( d->contents.contains( c ) ); d->contents.removeAll( c ); if ( del ) { delete c; } else { c->d_ptr->parent = 0; } // If only one content is left, turn this content into a single-part. if( d->contents.count() == 1 ) { Content *main = d->contents.first(); // Move all headers from the old subcontent to ourselves. // NOTE: This also sets the new Content-Type. foreach( Headers::Base *h, main->h_eaders ) { setHeader( h ); // Will remove the old one if present. } main->h_eaders.clear(); // Move the body. d->body = main->body(); // Delete the old subcontent. delete main; d->contents.clear(); } } void Content::changeEncoding( Headers::contentEncoding e ) { Headers::ContentTransferEncoding *enc = contentTransferEncoding(); if( enc->encoding() == e ) { // Nothing to do. return; } if( decodeText() ) { // This is textual content. Textual content is stored decoded. Q_ASSERT( enc->decoded() ); enc->setEncoding( e ); } else { // This is non-textual content. Re-encode it. if( e == Headers::CEbase64 ) { d_ptr->body = KCodecs::base64Encode( decodedContent(), true ); d_ptr->body.append( "\n" ); enc->setEncoding( e ); enc->setDecoded( false ); } else { // It only makes sense to convert binary stuff to base64. Q_ASSERT( false ); } } } void Content::toStream( QTextStream &ts, bool scrambleFromLines ) { QByteArray ret = encodedContent( false ); if ( scrambleFromLines ) { // FIXME Why are only From lines with a preceding empty line considered? // And, of course, all lines starting with >*From have to be escaped // because otherwise the transformation is not revertable. ret.replace( "\n\nFrom ", "\n\n>From "); } ts << ret; } Headers::Generic *Content::getNextHeader( QByteArray &head ) { return nextHeader( head ); } Headers::Generic *Content::nextHeader( QByteArray &head ) { Headers::Base *header = HeaderParsing::extractFirstHeader( head ); // Convert it from the real class to Generic. Headers::Generic *ret = new Headers::Generic( header->type(), this ); ret->from7BitString( header->as7BitString() ); return ret; } Headers::Base *Content::getHeaderByType( const char *type ) { return headerByType( type ); } Headers::Base *Content::headerByType( const char *type ) { Q_ASSERT( type && *type ); foreach( Headers::Base *h, h_eaders ) { if( h->is( type ) ) { return h; // Found. } } return 0; // Not found. } Headers::Base::List Content::headersByType( const char *type ) { Q_ASSERT( type && *type ); Headers::Base::List result; foreach( Headers::Base *h, h_eaders ) { if( h->is( type ) ) { result << h; } } return result; } void Content::setHeader( Headers::Base *h ) { Q_ASSERT( h ); removeHeader( h->type() ); appendHeader( h ); } void Content::appendHeader( Headers::Base *h ) { h_eaders.append( h ); h->setParent( this ); } void Content::prependHeader( Headers::Base *h ) { h_eaders.prepend( h ); h->setParent( this ); } bool Content::removeHeader( const char *type ) { for ( Headers::Base::List::iterator it = h_eaders.begin(); it != h_eaders.end(); ++it ) if ( (*it)->is(type) ) { delete (*it); h_eaders.erase( it ); return true; } return false; } bool Content::hasHeader( const char *type ) { return headerByType( type ) != 0; } int Content::size() { int ret = d_ptr->body.length(); if ( contentTransferEncoding()->encoding() == Headers::CEbase64 ) { return ret * 3 / 4; //base64 => 6 bit per byte } // Not handling quoted-printable here since that requires actually // converting the content, and that is O(size_of_content). // For quoted-printable, this is only an approximate size. return ret; } int Content::storageSize() const { const Q_D(Content); int s = d->head.size(); if ( d->contents.isEmpty() ) { s += d->body.size(); } else { foreach ( Content *c, d->contents ) { s += c->storageSize(); } } return s; } int Content::lineCount() const { const Q_D(Content); int ret = 0; if ( !isTopLevel() ) { ret += d->head.count( '\n' ); } ret += d->body.count( '\n' ); foreach ( Content *c, d->contents ) { ret += c->lineCount(); } return ret; } QByteArray Content::rawHeader( const char *name ) const { return KMime::extractHeader( d_ptr->head, name ); } QList Content::rawHeaders( const char *name ) const { return KMime::extractHeaders( d_ptr->head, name ); } bool Content::decodeText() { Q_D(Content); Headers::ContentTransferEncoding *enc = contentTransferEncoding(); if ( !contentType()->isText() ) { return false; //non textual data cannot be decoded here => use decodedContent() instead } if ( enc->decoded() ) { return true; //nothing to do } switch( enc->encoding() ) { case Headers::CEbase64 : d->body = KCodecs::base64Decode( d->body ); d->body.append( "\n" ); break; case Headers::CEquPr : d->body = KCodecs::quotedPrintableDecode( d->body ); break; case Headers::CEuuenc : d->body = KCodecs::uudecode( d->body ); d->body.append( "\n" ); break; case Headers::CEbinary : // nothing to decode d->body.append( "\n" ); default : break; } enc->setDecoded( true ); return true; } QByteArray Content::defaultCharset() const { return d_ptr->defaultCS; } void Content::setDefaultCharset( const QByteArray &cs ) { d_ptr->defaultCS = KMime::cachedCharset( cs ); foreach ( Content *c, d_ptr->contents ) { c->setDefaultCharset( cs ); } // reparse the part and its sub-parts in order // to clear cached header values parse(); } bool Content::forceDefaultCharset() const { return d_ptr->forceDefaultCS; } void Content::setForceDefaultCharset( bool b ) { d_ptr->forceDefaultCS = b; foreach ( Content *c, d_ptr->contents ) { c->setForceDefaultCharset( b ); } // reparse the part and its sub-parts in order // to clear cached header values parse(); } Content * KMime::Content::content( const ContentIndex &index ) const { if ( !index.isValid() ) { return const_cast( this ); } ContentIndex idx = index; unsigned int i = idx.pop() - 1; // one-based -> zero-based index if ( i < (unsigned int)d_ptr->contents.size() ) { return d_ptr->contents[i]->content( idx ); } else { return 0; } } ContentIndex KMime::Content::indexForContent( Content * content ) const { int i = d_ptr->contents.indexOf( content ); if ( i >= 0 ) { ContentIndex ci; ci.push( i + 1 ); // zero-based -> one-based index return ci; } // not found, we need to search recursively for ( int i = 0; i < d_ptr->contents.size(); ++i ) { ContentIndex ci = d_ptr->contents[i]->indexForContent( content ); if ( ci.isValid() ) { // found it ci.push( i + 1 ); // zero-based -> one-based index return ci; } } return ContentIndex(); // not found } bool Content::isTopLevel() const { return false; } void Content::setParent( Content* parent ) { //make sure the Content is only in the contents list of one parent object Content *oldParent = d_ptr->parent; if ( oldParent && oldParent->contents().contains( this ) ) { oldParent->removeContent( this ); } d_ptr->parent = parent; if ( parent && !parent->contents().contains( this ) ) { parent->addContent( this ); } } Content* Content::parent() const { return d_ptr->parent; } Content* Content::topLevel() const { Content *top = const_cast(this); Content *c = parent(); while ( c ) { top = c; c = c->parent(); } return top; } ContentIndex Content::index() const { Content* top = topLevel(); if ( top ) { return top->indexForContent( const_cast(this) ); } return indexForContent( const_cast(this) ); } // @cond PRIVATE #define kmime_mk_header_accessor( type, method ) \ Headers::type *Content::method( bool create ) { \ return header( create ); \ } kmime_mk_header_accessor( ContentType, contentType ) kmime_mk_header_accessor( ContentTransferEncoding, contentTransferEncoding ) kmime_mk_header_accessor( ContentDisposition, contentDisposition ) kmime_mk_header_accessor( ContentDescription, contentDescription ) kmime_mk_header_accessor( ContentLocation, contentLocation ) kmime_mk_header_accessor( ContentID, contentID ) #undef kmime_mk_header_accessor // @endcond bool ContentPrivate::parseUuencoded() { Q_Q( Content ); Parser::UUEncoded uup( body, KMime::extractHeader( head, "Subject" ) ); if( !uup.parse() ) { return false; // Parsing failed. } Headers::ContentType *ct = q->contentType(); ct->clear(); if( uup.isPartial() ) { // This seems to be only a part of the message, so we treat it as "message/partial". ct->setMimeType( "message/partial" ); //ct->setId( uniqueString() ); not needed yet ct->setPartialParams( uup.partialCount(), uup.partialNumber() ); q->contentTransferEncoding()->setEncoding( Headers::CE7Bit ); } else { // This is a complete message, so treat it as "multipart/mixed". body.clear(); ct->setMimeType( "multipart/mixed" ); ct->setBoundary( multiPartBoundary() ); ct->setCategory( Headers::CCcontainer ); q->contentTransferEncoding()->clear(); // 7Bit, decoded. // Add the plain text part first. Q_ASSERT( contents.count() == 0 ); { Content *c = new Content( q ); c->contentType()->setMimeType( "text/plain" ); c->contentTransferEncoding()->setEncoding( Headers::CE7Bit ); c->setBody( uup.textPart() ); contents.append( c ); } // Now add each of the binary parts as sub-Contents. for( int i = 0; i < uup.binaryParts().count(); ++i ) { Content *c = new Content( q ); c->contentType()->setMimeType( uup.mimeTypes().at( i ) ); c->contentType()->setName( uup.filenames().at( i ), QByteArray( /*charset*/ ) ); c->contentTransferEncoding()->setEncoding( Headers::CEuuenc ); c->contentDisposition()->setDisposition( Headers::CDattachment ); c->contentDisposition()->setFilename( uup.filenames().at( i ) ); c->setBody( uup.binaryParts().at( i ) ); c->changeEncoding( Headers::CEbase64 ); // Convert to base64. contents.append( c ); } } return true; // Parsing successful. } bool ContentPrivate::parseYenc() { Q_Q( Content ); Parser::YENCEncoded yenc( body ); if( !yenc.parse() ) { return false; // Parsing failed. } Headers::ContentType *ct = q->contentType(); ct->clear(); if( yenc.isPartial() ) { // Assume there is exactly one decoded part. Treat this as "message/partial". ct->setMimeType( "message/partial" ); //ct->setId( uniqueString() ); not needed yet ct->setPartialParams( yenc.partialCount(), yenc.partialNumber() ); q->contentTransferEncoding()->setEncoding( Headers::CEbinary ); q->changeEncoding( Headers::CEbase64 ); // Convert to base64. } else { // This is a complete message, so treat it as "multipart/mixed". body.clear(); ct->setMimeType( "multipart/mixed" ); ct->setBoundary( multiPartBoundary() ); ct->setCategory( Headers::CCcontainer ); q->contentTransferEncoding()->clear(); // 7Bit, decoded. // Add the plain text part first. Q_ASSERT( contents.count() == 0 ); { Content *c = new Content( q ); c->contentType()->setMimeType( "text/plain" ); c->contentTransferEncoding()->setEncoding( Headers::CE7Bit ); c->setBody( yenc.textPart() ); contents.append( c ); } // Now add each of the binary parts as sub-Contents. for ( int i=0; icontentType()->setMimeType( yenc.mimeTypes().at( i ) ); c->contentType()->setName( yenc.filenames().at( i ), QByteArray( /*charset*/ ) ); c->contentTransferEncoding()->setEncoding( Headers::CEbinary ); c->contentDisposition()->setDisposition( Headers::CDattachment ); c->contentDisposition()->setFilename( yenc.filenames().at( i ) ); c->setBody( yenc.binaryParts().at( i ) ); // Yenc bodies are binary. c->changeEncoding( Headers::CEbase64 ); // Convert to base64. contents.append( c ); } } return true; // Parsing successful. } bool ContentPrivate::parseMultipart() { Q_Q( Content ); const Headers::ContentType *ct = q->contentType(); const QByteArray boundary = ct->boundary(); if( boundary.isEmpty() ) { return false; // Parsing failed; invalid multipart content. } Parser::MultiPart mpp( body, boundary ); if( !mpp.parse() ) { return false; // Parsing failed. } // Determine the category of the subparts (used in attachments()). Headers::contentCategory cat; if( ct->isSubtype( "alternative" ) ) { cat = Headers::CCalternativePart; } else { cat = Headers::CCmixedPart; // Default to "mixed". } // Create a sub-Content for every part. Q_ASSERT( contents.isEmpty() ); body.clear(); QList parts = mpp.parts(); foreach( const QByteArray &part, mpp.parts() ) { Content *c = new Content( q ); c->setContent( part ); c->setFrozen( frozen ); c->parse(); c->contentType()->setCategory( cat ); contents.append( c ); } return true; // Parsing successful. } } // namespace KMime diff --git a/kmime/kmime_content.h b/kmime/kmime_content.h index 767d1caee..9584d9f07 100644 --- a/kmime/kmime_content.h +++ b/kmime/kmime_content.h @@ -1,684 +1,684 @@ /* kmime_content.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause Copyright (c) 2009 Constantin Berzan 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 API for handling @ref MIME data and defines the Content class. @brief Defines the Content class. @authors the KMime authors (see AUTHORS file), Volker Krause \ TODO: possible glossary terms: content encoding, transfer type, disposition, description header body attachment charset article string representation broken-down object representation */ #ifndef __KMIME_CONTENT_H__ #define __KMIME_CONTENT_H__ #include #include #include #include "kmime_export.h" #include "kmime_contentindex.h" #include "kmime_util.h" #include "kmime_headers.h" namespace KMime { class ContentPrivate; /** @brief A class that encapsulates @ref MIME encoded Content. A Content object holds two representations of a content: - the string representation: This is the content encoded as a string ready for transport. Accessible through the encodedContent() method. - the broken-down representation: This is the tree of objects (headers, sub-Contents) that this Content is made of. Accessible through methods like header() and contents(). The parse() function updates the broken-down representation of the Content from its string representation. Calling it is necessary to access the headers or sub-Contents of this Content. The assemble() function updates the string representation of the Content from its broken-down representation. Calling it is necessary for encodedContent() to reflect any changes made to the Content. */ /* KDE5: * Do not convert singlepart <-> multipart automatically. * A bunch of methods probably don't need to be virtual (since they're not needed in either Message or NewsArticle). */ class KMIME_EXPORT Content { public: /** Describes a list of Content objects. */ typedef QList List; /** Creates an empty Content object. */ Content(); /** Creates an empty Content object with a specified parent. @param parent the parent Content object @since 4.3 */ explicit Content( Content* parent ); // KDE5: Merge with the above. /** Creates a Content object containing the given raw data. @param head is a QByteArray containing the header data. @param body is a QByteArray containing the body data. */ Content( const QByteArray &head, const QByteArray &body ); /** Creates a Content object containing the given raw data. @param head is a QByteArray containing the header data. @param body is a QByteArray containing the body data. @param parent the parent Content object @since 4.3 */ // KDE5: Merge with the above. Content( const QByteArray &head, const QByteArray &body, Content *parent ); /** Destroys this Content object. */ virtual ~Content(); /** Returns true if this Content object is not empty. */ bool hasContent() const; /** Sets the Content to the given raw data, containing the Content head and body separated by two linefeeds. This method operates on the string representation of the Content. Call parse() if you want to access individual headers or sub-Contents. @param l is a line-splitted list of the raw Content data. */ void setContent( const QList &l ); /** Sets the Content to the given raw data, containing the Content head and body separated by two linefeeds. This method operates on the string representation of the Content. Call parse() if you want to access individual headers or sub-Contents. @param s is a QByteArray containing the raw Content data. */ void setContent( const QByteArray &s ); /** Parses the Content. This means the broken-down object representation of the Content is updated from the string representation of the Content. Call this if you want to access or change headers or sub-Contents. */ virtual void parse(); /** Returns whether this Content is frozen. A frozen content is immutable, i.e. calling assemble() will never modify its head or body, and encodedContent() will return the same data before and after parsing. @since 4.4. @see setFrozen(). */ bool isFrozen() const; /** Freezes this Content if @p frozen is true; otherwise unfreezes it. @since 4.4 @see isFrozen(). */ void setFrozen( bool frozen = true ); /** Generates the MIME content. This means the string representation of this Content is updated from the broken-down object representation. Call this if you have made changes to the content, and want encodedContent() to reflect those changes. @note assemble() has no effect if the Content isFrozen(). You may want to freeze, for instance, signed sub-Contents, to make sure they are kept unmodified. @warning assemble() may change the order of the headers, and other details such as where folding occurs. This may break things like signature verification, so you should *ONLY* call assemble() when you have actually modified the content. */ virtual void assemble(); /** Clears the content, deleting all headers and sub-Contents. */ // KDE5: make non-virtual. virtual void clear(); /** Removes all sub-Contents from this content. Deletes them if @p del is true. This is different from calling removeContent() on each sub-Content, because removeContent() will convert this to a single-part Content if only one sub-Content is left. Calling clearContents() does NOT make this Content single-part. @param del Whether to delete the sub-Contents. @see removeContent() @since 4.4 */ void clearContents( bool del = true ); /** Returns the Content header raw data. @see setHead(). */ QByteArray head() const; /** Sets the Content header raw data. This method operates on the string representation of the Content. Call parse() if you want to access individual headers. @param head is a QByteArray containing the header data. @see head(). */ void setHead( const QByteArray &head ); /** Extracts and removes the next header from @p head. The caller is responsible for deleting the returned header. @deprecated Use nextHeader( QByteArray ) @param head is a QByteArray containing the header data. */ KDE_DEPRECATED Headers::Generic *getNextHeader( QByteArray &head ); /** Extracts and removes the next header from @p head. The caller is responsible for deleting the returned header. @since 4.2 @deprecated Use KMime::extractFirstHeader(). @param head is a QByteArray containing the header data. */ // KDE5: Remove this. This method has nothing to do with *this object. KDE_DEPRECATED Headers::Generic *nextHeader( QByteArray &head ); /** Tries to find a @p type header in the Content and returns it. @deprecated Use headerByType( const char * ) */ // KDE5: Make non-virtual. KDE_DEPRECATED virtual Headers::Base *getHeaderByType( const char *type ); /** Returns the first header of type @p type, if it exists. Otherwise returns 0. Note that the returned header may be empty. @since 4.2 */ // KDE5: Make non-virtual. virtual Headers::Base *headerByType( const char *type ); /** Returns the first header of type T, if it exists. If the header does not exist and @p create is true, creates an empty header and returns it. Otherwise returns 0. Note that the returned header may be empty. @param create Whether to create the header if it does not exist. @since 4.4. */ template T *header( bool create = false ); /** Returns all @p type headers in the Content. Take care that this result is not cached, so could be slow. @since 4.2 */ virtual QList headersByType( const char *type ); /** Sets the specified header to this Content. Any previous header of the same type is removed. If you need multiple headers of the same type, use appendHeader() or prependHeader(). @param h The header to set. @see appendHeader() @see removeHeader() @since 4.4 */ // KDE5: make non-virtual. virtual void setHeader( Headers::Base *h ); /** Appends the specified header to the headers of this Content. @param h The header to append. @since 4.4 */ void appendHeader( Headers::Base *h ); /** Prepends the specified header to the headers of this Content. @param h The header to prepend. @since 4.4 */ void prependHeader( Headers::Base *h ); /** Searches for the first header of type @p type, and deletes it, removing it from this Content. @param type The type of the header to look for. @return true if a header was found and removed. */ // TODO probably provide removeHeader() too. // KDE5: make non-virtual. virtual bool removeHeader( const char *type ); /** @return true if this Content has a header of type @p type. @param type The type of the header to look for. */ // TODO probably provide hasHeader() too. bool hasHeader( const char *type ); /** Returns the Content-Type header. @param create If true, create the header if it doesn't exist yet. */ Headers::ContentType *contentType( bool create = true ); /** Returns the Content-Transfer-Encoding header. @param create If true, create the header if it doesn't exist yet. */ Headers::ContentTransferEncoding *contentTransferEncoding( bool create = true ); /** Returns the Content-Disposition header. @param create If true, create the header if it doesn't exist yet. */ Headers::ContentDisposition *contentDisposition( bool create = true ); /** Returns the Content-Description header. @param create If true, create the header if it doesn't exist yet. */ Headers::ContentDescription *contentDescription( bool create = true ); /** Returns the Content-Location header. @param create If true, create the header if it doesn't exist yet. @since 4.2 */ Headers::ContentLocation *contentLocation( bool create = true ); /** Returns the Content-ID header. @param create if true, create the header if it does not exist yet. @since 4.4 */ Headers::ContentID *contentID( bool create = true ); /** Returns the size of the Content body after encoding. (If the encoding is quoted-printable, this is only an approximate size.) */ int size(); /** Returns the size of this Content and all sub-Contents. */ int storageSize() const; /** Line count of this Content and all sub-Contents. */ int lineCount() const; /** Returns the Content body raw data. @see setBody(). */ QByteArray body() const; /** Sets the Content body raw data. This method operates on the string representation of the Content. Call parse() if you want to access individual sub-Contents. @param body is a QByteArray containing the body data. @see body(). */ void setBody( const QByteArray &body ); /** Returns a QByteArray containing the encoded Content, including the Content header and all sub-Contents. @param useCrLf If true, use @ref CRLF instead of @ref LF for linefeeds. */ QByteArray encodedContent( bool useCrLf = false ); /** Returns the decoded Content body. */ QByteArray decodedContent(); /** Returns the decoded text. Additional to decodedContent(), this also applies charset decoding. If this is not a text Content, decodedText() returns an empty QString. @param trimText If true, then the decoded text will have all trailing whitespace removed. @param removeTrailingNewlines If true, then the decoded text will have all consecutive trailing newlines removed. The last trailing new line of the decoded text is always removed. */ QString decodedText( bool trimText = false, bool removeTrailingNewlines = false ); /** Sets the Content body to the given string using the current charset. @param s Unicode-encoded string. */ void fromUnicodeString( const QString &s ); /** Returns the first Content with mimetype text/. */ Content *textContent(); /** Returns a list of attachments. @param incAlternatives If true, include multipart/alternative parts. */ List attachments( bool incAlternatives = false ); /** Returns a list of sub-Contents. */ List contents() const; /** Adds a new sub-Content. If the sub-Content is already part of another Content object, it is removed from there and its parent is updated. If the current Content object is single-part, it is converted to multipart/mixed first. @warning If the single-part to multipart conversion happens, all pointers you may have into this object (such as headers) will become invalid! @param content The new sub-Content. @param prepend If true, prepend to the Content list; otherwise append. to the Content list. @see removeContent(). */ // KDE5: Do not convert single-part->multipart automatically. void addContent( Content *content, bool prepend = false ); /** Removes the given sub-Content. If only one sub-Content is left, the current Content object is converted into a single-part Content. @warning If the multipart to single-part conversion happens, the head and body of the single remaining sub-Content are copied over, and the sub-Content is deleted. All pointers to it or into it (such as headers) will become invalid! @param content The Content to remove. @param del If true, delete the removed Content object. Otherwise set its parent to 0. @see addContent(). @see clearContents(). */ // KDE5: Do not convert multipart->single-part automatically. void removeContent( Content *content, bool del = false ); /** Changes the encoding of this Content to @p e. If the Content is binary, this actually re-encodes the data to use the new encoding. @param e The new encoding to use. */ void changeEncoding( Headers::contentEncoding e ); /** Saves the encoded Content to the given textstream @param ts is the stream where the Content should be written to. @param scrambleFromLines: If true, replace "\nFrom " with "\n>From " in the stream. This is needed to avoid problem with mbox-files */ void toStream( QTextStream &ts, bool scrambleFromLines = false ); // NOTE: The charset methods below are accessed by the headers which // have this Content as a parent. /** Returns the charset that is used for all headers and the body if the charset is not declared explictly. @see setDefaultCharset() */ QByteArray defaultCharset() const; /** Sets the default charset. @param cs is a QByteArray containing the new default charset. @see defaultCharset(). */ void setDefaultCharset( const QByteArray &cs ); /** Use the default charset even if a different charset is declared in the article. @see setForceDefaultCharset(). */ bool forceDefaultCharset() const; /** Enables/disables the force mode, housekeeping. works correctly only when the article is completely empty or completely loaded. @param b If true, force the default charset to be used. @see forceDefaultCharset(). */ virtual void setForceDefaultCharset( bool b ); /** Returns the Content specified by the given index. If the index does not point to a Content, 0 is returned. If the index is invalid (empty), this Content is returned. @param index The Content index. */ Content *content( const ContentIndex &index ) const; /** Returns the ContentIndex for the given Content, or an invalid index if the Content is not found within the hierarchy. @param content the Content object to search. */ ContentIndex indexForContent( Content *content ) const; /** Returns true if this is the top-level node in the MIME tree, i.e. if this is actually a Message or NewsArticle. */ virtual bool isTopLevel() const; /** * Sets a new parent to the Content and add to its contents list. If it already had a parent, it is removed from the * old parents contents list. * @param parent the new parent * @since 4.3 */ void setParent( Content *parent ); /** * Returns the parent content object, or 0 if the content doesn't have a parent. * @since 4.3 */ Content* parent() const; /** * Returns the toplevel content object, 0 if there is no such object. * @since 4.3 */ Content* topLevel() const; /** * Returns the index of this Content based on the topLevel() object. * @since 4.3 */ ContentIndex index() const; protected: /** Reimplement this method if you need to assemble additional headers in a derived class. Don't forget to call the implementation of the base class. @return The raw, assembled headers. */ virtual QByteArray assembleHeaders(); /** Returns the raw string representing the header of type @p name. @deprecated Use KMime::extractHeader() directly instead. */ KDE_DEPRECATED QByteArray rawHeader( const char *name ) const; /** Returns a list of raw strings representing all header of type @p name. @deprecated Use KMime::extractHeaders() directly instead. */ KDE_DEPRECATED QList rawHeaders( const char *name ) const; /** Returns whether this object holds text content. */ // KDE5: Not needed outside. Move to Private class. bool decodeText(); /** Returns the first header of type T, if it exists. @deprecated Use header() instead. */ template KDE_DEPRECATED T *headerInstance( T *ptr, bool create ); /** The list of headers in this Content. Do not use this directly. */ // KDE5: Not needed outside. Move to Private class. Headers::Base::List h_eaders; //@cond PRIVATE ContentPrivate *d_ptr; explicit Content( ContentPrivate *d ); //@endcond private: Q_DECLARE_PRIVATE( Content ) Q_DISABLE_COPY( Content ) }; // some compilers (for instance Compaq C++) need template inline functions // here rather than in the *.cpp file template T *Content::headerInstance( T *ptr, bool create ) { return header( create ); } template T *Content::header( bool create ) { T dummy; Headers::Base *h = headerByType( dummy.type() ); if( h ) { // Make sure the header is actually of the right type. Q_ASSERT( dynamic_cast( h ) ); } else if( create ) { h = new T( this ); setHeader( h ); } return static_cast( h ); } } // namespace KMime #endif // __KMIME_CONTENT_H__ diff --git a/kmime/kmime_dateformatter.cpp b/kmime/kmime_dateformatter.cpp index 93454e4b9..82120e7f3 100644 --- a/kmime/kmime_dateformatter.cpp +++ b/kmime/kmime_dateformatter.cpp @@ -1,336 +1,336 @@ /* kmime_dateformatter.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 API for handling @ref MIME data and defines the DateFormatter class. @brief Defines the DateFormatter class. @authors the KMime authors (see AUTHORS file) */ #include "kmime_dateformatter.h" #include #include // for abs() #include #include #include #include using namespace KMime; //@cond PRIVATE int DateFormatter::mDaylight = -1; //@endcond DateFormatter::DateFormatter( FormatType ftype ) : mFormat( ftype ), mTodayOneSecondBeforeMidnight( 0 ) { } DateFormatter::~DateFormatter() { } DateFormatter::FormatType DateFormatter::format() const { return mFormat; } void DateFormatter::setFormat( FormatType ftype ) { mFormat = ftype; } QString DateFormatter::dateString( time_t t , const QString &lang , bool shortFormat, bool includeSecs ) const { switch ( mFormat ) { case Fancy: return fancy( t ); break; case Localized: return localized( t, shortFormat, includeSecs, lang ); break; case CTime: return cTime( t ); break; case Iso: return isoDate( t ); break; case Rfc: return rfc2822( t ); break; case Custom: return custom( t ); break; } return QString(); } QString DateFormatter::dateString( const QDateTime &dt, const QString &lang, bool shortFormat, bool includeSecs ) const { return dateString( qdateToTimeT( dt ), lang, shortFormat, includeSecs ); } QString DateFormatter::rfc2822( time_t t ) const { QDateTime tmp; QString ret; tmp.setTime_t( t ); ret = tmp.toString( "ddd, dd MMM yyyy hh:mm:ss " ).toLatin1(); ret += zone( t ); return ret; } QString DateFormatter::custom( time_t t ) const { if ( mCustomFormat.isEmpty() ) { return QString(); } int z = mCustomFormat.indexOf( 'Z' ); QDateTime d; QString ret = mCustomFormat; d.setTime_t( t ); if ( z != -1 ) { ret.replace( z, 1, zone( t ) ); } ret = d.toString( ret ); return ret; } void DateFormatter::setCustomFormat( const QString &format ) { mCustomFormat = format; mFormat = Custom; } QString DateFormatter::customFormat() const { return mCustomFormat; } QByteArray DateFormatter::zone( time_t t ) const { #if defined(HAVE_TIMEZONE) || defined(HAVE_TM_GMTOFF) struct tm *local = localtime( &t ); #endif #if defined(HAVE_TIMEZONE) //hmm, could make hours & mins static int secs = abs( timezone ); int neg = (timezone > 0) ? 1 : 0; int hours = secs / 3600; int mins = (secs - hours*3600) / 60; // adjust to daylight if ( local->tm_isdst > 0 ) { mDaylight = 1; if ( neg ) { --hours; } else { ++hours; } } else { mDaylight = 0; } #elif defined(HAVE_TM_GMTOFF) int secs = abs( local->tm_gmtoff ); int neg = (local->tm_gmtoff < 0) ? 1 : 0; int hours = secs / 3600; int mins = (secs - hours * 3600) / 60; if ( local->tm_isdst > 0 ) { mDaylight = 1; } else { mDaylight = 0; } #else QDateTime d1 = QDateTime::fromString( asctime( gmtime( &t ) ) ); QDateTime d2 = QDateTime::fromString( asctime( localtime( &t ) ) ); int secs = d1.secsTo( d2 ); int neg = ( secs < 0 ) ? 1 : 0; secs = abs( secs ); int hours = secs / 3600; int mins = (secs - hours * 3600) / 60; // daylight should be already taken care of here #endif /* HAVE_TIMEZONE */ QByteArray ret; QTextStream s( &ret, QIODevice::WriteOnly ); s << ( neg ? '-' : '+' ) << qSetFieldWidth( 2 ) << qSetPadChar( '0' ) << right << hours << mins; //old code: ret.sprintf( "%c%.2d%.2d", (neg) ? '-' : '+', hours, mins ); return ret; } time_t DateFormatter::qdateToTimeT( const QDateTime &dt ) const { QDateTime epoch( QDate( 1970, 1, 1 ), QTime( 00, 00, 00 ) ); time_t t; time( &t ); QDateTime d1 = QDateTime::fromString( asctime( gmtime( &t ) ) ); QDateTime d2 = QDateTime::fromString( asctime( localtime( &t ) ) ); time_t drf = epoch.secsTo( dt ) - d1.secsTo( d2 ); return drf; } QString DateFormatter::fancy( time_t t ) const { KLocale *locale = KGlobal::locale(); if ( t <= 0 ) { return i18nc( "invalid time specified", "unknown" ); } if ( mTodayOneSecondBeforeMidnight < time( 0 ) ) { // determine time_t value of today 23:59:59 const QDateTime today( QDate::currentDate(), QTime( 23, 59, 59 ) ); mTodayOneSecondBeforeMidnight = today.toTime_t(); } QDateTime old; old.setTime_t( t ); if ( mTodayOneSecondBeforeMidnight >= t ) { const time_t diff = mTodayOneSecondBeforeMidnight - t; if ( diff < 7 * 24 * 60 * 60 ) { if ( diff < 24 * 60 * 60 ) { return i18n( "Today %1", locale->formatTime( old.time(), true ) ); } if ( diff < 2 * 24 * 60 * 60 ) { return i18n( "Yesterday %1", locale->formatTime( old.time(), true ) ); } for ( int i = 3; i < 8; i++ ) { if ( diff < i * 24 * 60 * 60 ) { return i18nc( "1. weekday, 2. time", "%1 %2" , locale->calendar()->weekDayName( old.date() ) , locale->formatTime( old.time(), true ) ); } } } } return locale->formatDateTime( old ); } QString DateFormatter::localized( time_t t, bool shortFormat, bool includeSecs, const QString &lang ) const { QDateTime tmp; QString ret; KLocale *locale = KGlobal::locale(); tmp.setTime_t( t ); if ( !lang.isEmpty() ) { locale = new KLocale( lang, lang, lang); ret = locale->formatDateTime( tmp, (shortFormat ? KLocale::ShortDate : KLocale::LongDate), includeSecs ); delete locale; } else { ret = locale->formatDateTime( tmp, (shortFormat ? KLocale::ShortDate : KLocale::LongDate), includeSecs ); } return ret; } QString DateFormatter::cTime( time_t t ) const { return QString::fromLatin1( ctime( &t ) ).trimmed(); } QString DateFormatter::isoDate( time_t t ) const { char cstr[64]; strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime( &t ) ); return QString( cstr ); } void DateFormatter::reset() { mTodayOneSecondBeforeMidnight = 0; } QString DateFormatter::formatDate( FormatType ftype, time_t t, const QString &data, bool shortFormat, bool includeSecs ) { DateFormatter f( ftype ); if ( ftype == Custom ) { f.setCustomFormat( data ); } return f.dateString( t, data, shortFormat, includeSecs ); } QString DateFormatter::formatCurrentDate( FormatType ftype, const QString &data, bool shortFormat, bool includeSecs ) { DateFormatter f( ftype ); if ( ftype == Custom ) { f.setCustomFormat( data ); } return f.dateString( time( 0 ), data, shortFormat, includeSecs ); } bool DateFormatter::isDaylight() { if ( mDaylight == -1 ) { time_t ntime = time( 0 ); struct tm *local = localtime( &ntime ); if ( local->tm_isdst > 0 ) { mDaylight = 1; return true; } else { mDaylight = 0; return false; } } else if ( mDaylight != 0 ) { return true; } else { return false; } } diff --git a/kmime/kmime_dateformatter.h b/kmime/kmime_dateformatter.h index 1c5b834ea..35b4431ee 100644 --- a/kmime/kmime_dateformatter.h +++ b/kmime/kmime_dateformatter.h @@ -1,295 +1,295 @@ /* -*- c++ -*- kmime_dateformatter.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 API for handling @ref MIME data and defines the DateFormatter class. @brief Defines the DateFormatter class. @authors the KMime authors (see AUTHORS file) @glossary @anchor RFC2822 @anchor rfc2822 @b RFC @b 2822: RFC that defines the Internet Message Format. @glossary @anchor ISO8601 @anchor iso8601 @b ISO @b 8601: International Standards Organization (ISO) standard that defines the international standard for date and time representations. @glossary @anchor ctime @b ctime: a Unix library call which returns the local time as a human readable ASCII string of the form "Sun Mar 31 02:08:35 2002". */ #ifndef __KMIME_DATEFORMATTER_H__ #define __KMIME_DATEFORMATTER_H__ #include #include #include #include "kmime_export.h" namespace KMime { /** @brief A class for abstracting date formatting. This class deals with different kinds of date display formats. The formats supported include: - @b fancy "Today 02:08:35" - @b ctime as with the @ref ctime function, eg. "Sun Mar 31 02:08:35 2002" - @b localized according to the control center setting, eg. "2002-03-31 02:08" - @b iso according to the @ref ISO8601 standard, eg. "2002-03-31 02:08:35" - @b rfc according to @ref RFC2822 (Section 3.3), eg. "Sun, 31 Mar 2002 02:08:35 -0500" - @b custom "whatever you like" */ class KMIME_EXPORT DateFormatter { public: /** The different types of date formats. */ enum FormatType { CTime, /**< ctime "Sun Mar 31 02:08:35 2002" */ Localized, /**< localized "2002-03-31 02:08" */ Fancy, /**< fancy "Today 02:08:35" */ Iso, /**< iso "2002-03-31 02:08:35" */ Rfc, /**< rfc "Sun, 31 Mar 2002 02:08:35 -0500" */ Custom /**< custom "whatever you like" */ }; /** Constructs a date formatter with a default #FormatType. @param ftype is the default #FormatType to use. */ explicit DateFormatter( FormatType ftype=DateFormatter::Fancy ); /** Destroys the date formatter. */ ~DateFormatter(); /** Returns the #FormatType currently set. @see setFormat(). */ FormatType format() const; /** Sets the date format to @p ftype. @param ftype is the #FormatType. @see format(). */ void setFormat( FormatType ftype ); /** Constructs a formatted date string from time_t @p t. @param t is the time_t to use for formatting. @param lang is the language, only used if #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ QString dateString( time_t t, const QString &lang=QString(), bool shortFormat=true, bool includeSecs=false ) const; /** Constructs a formatted date string from QDateTime @p dtime. @param dtime is the QDateTime to use for formatting. @param lang is the language, only used if #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ QString dateString( const QDateTime &dtime, const QString &lang=QString(), bool shortFormat=true, bool includeSecs=false ) const; /** Sets the custom format for date to string conversions to @p format. This method accepts the same arguments as QDateTime::toString(), but also supports the "Z" expression which is substituted with the @ref RFC2822 (Section 3.3) style numeric timezone (-0500). @param format is a QString containing the custom format. @see QDateTime::toString(), customFormat(). */ void setCustomFormat( const QString &format ); /** Returns the custom format string. @see setCustomFormat(). */ QString customFormat() const; /** Resets the cached current date used for calculating the fancy date. This should be called whenever the current date changed, i.e. on midnight. @deprecated Can be safely removed. The date change is taken care of internally (as of 4.3). */ void reset(); //static methods /** Convenience function dateString @param ftype is the #FormatType to use. @param t is the time_t to use for formatting. @param data is either the format when #FormatType is Custom, or language when #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ static QString formatDate( DateFormatter::FormatType ftype, time_t t, const QString &data=QString(), bool shortFormat=true, bool includeSecs=false ); /** Convenience function, same as formatDate() but returns the current time formatted. @param ftype is the #FormatType to use. @param data is either the format when #FormatType is Custom, or language when #FormatType is #Localized. @param shortFormat if true, create the short version of the date string, only used if #FormatType is #Localized. @param includeSecs if true, include the seconds field in the date string, only used if #FormatType is #Localized. @return a QString containing the formatted date. */ static QString formatCurrentDate( DateFormatter::FormatType ftype, const QString &data=QString(), bool shortFormat=true, bool includeSecs=false ); /** Returns true if the current time is on daylight savings time; else false. */ static bool isDaylight(); protected: /** Returns a QString containing the specified time_t @p t formatted using the #Fancy #FormatType. @param t is the time_t to use for formatting. */ QString fancy( time_t t ) const ; /** Returns a QString containing the specified time_t @p t formatted using the #Localized #FormatType. @param t is the time_t to use for formatting. @param shortFormat if true, create the short version of the date string. @param includeSecs if true, include the seconds field in the date string. @param lang is a QString containing the language to use. */ QString localized( time_t t, bool shortFormat=true, bool includeSecs=false, const QString &lang=QString() ) const; /** Returns a QString containing the specified time_t @p t formatted with the ctime() function. @param t is the time_t to use for formatting. */ QString cTime( time_t t ) const; /** Returns a QString containing the specified time_t @p t in the "%Y-%m-%d %H:%M:%S" #Iso #FormatType. @param t is the time_t to use for formatting. */ QString isoDate( time_t t ) const; /** Returns a QString containing the specified time_t @p t in the #Rfc #FormatType. @param t is the time_t to use for formatting. */ QString rfc2822( time_t t ) const; /** Returns a QString containing the specified time_t @p t formatted with a previously specified custom format. @param t time used for formatting */ QString custom( time_t t ) const; /** Returns a QString that identifies the timezone (eg."-0500") of the specified time_t @p t. @param t time to compute timezone from. */ QByteArray zone( time_t t ) const; /** Converts QDateTime @p dt to a time_t value. @param dt is the QDateTime to be converted. @return the time_t equivalent of the specified QDateTime. */ time_t qdateToTimeT( const QDateTime &dt ) const; private: //@cond PRIVATE FormatType mFormat; mutable time_t mTodayOneSecondBeforeMidnight; mutable QDateTime mUnused; // KDE5: remove QString mCustomFormat; static int mDaylight; //@endcond }; } // namespace KMime #endif /* __KMIME_DATEFORMATTER_H__ */ diff --git a/kmime/kmime_header_parsing.cpp b/kmime/kmime_header_parsing.cpp index 6af61a7ed..66e447260 100644 --- a/kmime/kmime_header_parsing.cpp +++ b/kmime/kmime_header_parsing.cpp @@ -1,2101 +1,2101 @@ /* -*- c++ -*- kmime_header_parsing.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 "kmime_header_parsing.h" #include "kmime_codecs.h" #include "kmime_headerfactory.h" #include "kmime_headers.h" #include "kmime_util.h" #include "kmime_dateformatter.h" #include "kmime_warning.h" #include #include #include #include #include #include #include // for isdigit #include using namespace KMime; using namespace KMime::Types; namespace KMime { namespace Types { // QUrl::fromAce is extremely expensive, so only use it when necessary. // Fortunately, the presence of IDNA is readily detected with a substring match... static inline QString QUrl_fromAce_wrapper( const QString & domain ) { if ( domain.contains( QLatin1String( "xn--" ) ) ) return QUrl::fromAce( domain.toLatin1() ); else return domain; } static QString addr_spec_as_string( const AddrSpec & as, bool pretty ) { if ( as.isEmpty() ) { return QString(); } bool needsQuotes = false; QString result; result.reserve( as.localPart.length() + as.domain.length() + 1 ); for ( int i = 0 ; i < as.localPart.length() ; ++i ) { const char ch = as.localPart[i].toLatin1(); if ( ch == '.' || isAText( ch ) ) { result += ch; } else { needsQuotes = true; if ( ch == '\\' || ch == '"' ) { result += '\\'; } result += ch; } } const QString dom = pretty ? QUrl_fromAce_wrapper( as.domain ) : as.domain ; if ( needsQuotes ) { return '"' + result + "\"@" + dom; } else { return result + '@' + dom; } } QString AddrSpec::asString() const { return addr_spec_as_string( *this, false ); } QString AddrSpec::asPrettyString() const { return addr_spec_as_string( *this, true ); } bool AddrSpec::isEmpty() const { return localPart.isEmpty() && domain.isEmpty(); } QByteArray Mailbox::address() const { return mAddrSpec.asString().toLatin1(); } AddrSpec Mailbox::addrSpec() const { return mAddrSpec; } QString Mailbox::name() const { return mDisplayName; } void Mailbox::setAddress( const AddrSpec &addr ) { mAddrSpec = addr; } void Mailbox::setAddress( const QByteArray &addr ) { const char *cursor = addr.constData(); if ( !HeaderParsing::parseAngleAddr( cursor, cursor + addr.length(), mAddrSpec ) ) { if ( !HeaderParsing::parseAddrSpec( cursor, cursor + addr.length(), mAddrSpec ) ) { kWarning() << "Invalid address"; return; } } } void Mailbox::setName( const QString &name ) { mDisplayName = name; } void Mailbox::setNameFrom7Bit( const QByteArray &name, const QByteArray &defaultCharset ) { QByteArray cs; mDisplayName = decodeRFC2047String( name, cs, defaultCharset, false ); } bool Mailbox::hasAddress() const { return !mAddrSpec.isEmpty(); } bool Mailbox::hasName() const { return !mDisplayName.isEmpty(); } QString Mailbox::prettyAddress() const { if ( !hasName() ) { return address(); } QString s = name(); if ( hasAddress() ) { s += QLatin1String(" <") + address() + QLatin1Char('>'); } return s; } void Mailbox::fromUnicodeString( const QString &s ) { from7BitString( encodeRFC2047String( s, "utf-8", false ) ); } void Mailbox::from7BitString( const QByteArray &s ) { const char *cursor = s.constData(); HeaderParsing::parseMailbox( cursor, cursor + s.length(), *this ); } QByteArray KMime::Types::Mailbox::as7BitString( const QByteArray &encCharset ) const { if ( !hasName() ) { return address(); } QByteArray rv; if ( isUsAscii( name() ) ) { QByteArray tmp = name().toLatin1(); addQuotes( tmp, false ); rv += tmp; } else { rv += encodeRFC2047String( name(), encCharset, true ); } if ( hasAddress() ) { rv += " <" + address() + '>'; } return rv; } } // namespace Types namespace HeaderParsing { // parse the encoded-word (scursor points to after the initial '=') bool parseEncodedWord( const char* &scursor, const char * const send, QString &result, QByteArray &language, QByteArray &usedCS, const QByteArray &defaultCS, bool forceCS ) { // make sure the caller already did a bit of the work. assert( *(scursor-1) == '=' ); // // STEP 1: // scan for the charset/language portion of the encoded-word // char ch = *scursor++; if ( ch != '?' ) { // kDebug(5320) << "first"; KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // remember start of charset (ie. just after the initial "=?") and // language (just after the first '*') fields: const char * charsetStart = scursor; const char * languageStart = 0; // find delimiting '?' (and the '*' separating charset and language // tags, if any): for ( ; scursor != send ; scursor++ ) { if ( *scursor == '?') { break; } else if ( *scursor == '*' && languageStart == 0 ) { languageStart = scursor + 1; } } // not found? can't be an encoded-word! if ( scursor == send || *scursor != '?' ) { // kDebug(5320) << "second"; KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // extract the language information, if any (if languageStart is 0, // language will be null, too): QByteArray maybeLanguage( languageStart, scursor - languageStart ); // extract charset information (keep in mind: the size given to the // ctor is one off due to the \0 terminator): QByteArray maybeCharset( charsetStart, ( languageStart ? languageStart - 1 : scursor ) - charsetStart ); // // STEP 2: // scan for the encoding portion of the encoded-word // // remember start of encoding (just _after_ the second '?'): scursor++; const char * encodingStart = scursor; // find next '?' (ending the encoding tag): for ( ; scursor != send ; scursor++ ) { if ( *scursor == '?' ) { break; } } // not found? Can't be an encoded-word! if ( scursor == send || *scursor != '?' ) { // kDebug(5320) << "third"; KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // extract the encoding information: QByteArray maybeEncoding( encodingStart, scursor - encodingStart ); // kDebug(5320) << "parseEncodedWord: found charset == \"" << maybeCharset // << "\"; language == \"" << maybeLanguage // << "\"; encoding == \"" << maybeEncoding << "\""; // // STEP 3: // scan for encoded-text portion of encoded-word // // remember start of encoded-text (just after the third '?'): scursor++; const char * encodedTextStart = scursor; // find the '?=' sequence (ending the encoded-text): for ( ; scursor != send ; scursor++ ) { if ( *scursor == '?' ) { if ( scursor + 1 != send ) { if ( *( scursor + 1 ) != '=' ) { // We expect a '=' after the '?', but we got something else; ignore KMIME_WARN << "Stray '?' in q-encoded word, ignoring this."; continue; } else { // yep, found a '?=' sequence scursor += 2; break; } } else { // The '?' is the last char, but we need a '=' after it! KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } } } if ( *( scursor - 2 ) != '?' || *( scursor - 1 ) != '=' || scursor < encodedTextStart + 2 ) { KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // set end sentinel for encoded-text: const char * const encodedTextEnd = scursor - 2; // // STEP 4: // setup decoders for the transfer encoding and the charset // // try if there's a codec for the encoding found: Codec * codec = Codec::codecForName( maybeEncoding ); if ( !codec ) { KMIME_WARN_UNKNOWN( Encoding, maybeEncoding ); return false; } // get an instance of a corresponding decoder: Decoder * dec = codec->makeDecoder(); assert( dec ); // try if there's a (text)codec for the charset found: bool matchOK = false; QTextCodec *textCodec = 0; if ( forceCS || maybeCharset.isEmpty() ) { textCodec = KGlobal::charsets()->codecForName( defaultCS, matchOK ); usedCS = cachedCharset( defaultCS ); } else { textCodec = KGlobal::charsets()->codecForName( maybeCharset, matchOK ); if ( !matchOK ) { //no suitable codec found => use default charset textCodec = KGlobal::charsets()->codecForName( defaultCS, matchOK ); usedCS = cachedCharset( defaultCS ); } else { usedCS = cachedCharset( maybeCharset ); } } if ( !matchOK || !textCodec ) { KMIME_WARN_UNKNOWN( Charset, maybeCharset ); delete dec; return false; }; // kDebug(5320) << "mimeName(): \"" << textCodec->name() << "\""; // allocate a temporary buffer to store the 8bit text: int encodedTextLength = encodedTextEnd - encodedTextStart; QByteArray buffer; buffer.resize( codec->maxDecodedSizeFor( encodedTextLength ) ); QByteArray::Iterator bit = buffer.begin(); QByteArray::ConstIterator bend = buffer.end(); // // STEP 5: // do the actual decoding // if ( !dec->decode( encodedTextStart, encodedTextEnd, bit, bend ) ) { KMIME_WARN << codec->name() << "codec lies about its maxDecodedSizeFor(" << encodedTextLength << ")\nresult may be truncated"; } result = textCodec->toUnicode( buffer.begin(), bit - buffer.begin() ); // kDebug(5320) << "result now: \"" << result << "\""; // cleanup: delete dec; language = maybeLanguage; return true; } static inline void eatWhiteSpace( const char* &scursor, const char * const send ) { while ( scursor != send && ( *scursor == ' ' || *scursor == '\n' || *scursor == '\t' || *scursor == '\r' ) ) scursor++; } bool parseAtom( const char * &scursor, const char * const send, QString &result, bool allow8Bit ) { QPair maybeResult; if ( parseAtom( scursor, send, maybeResult, allow8Bit ) ) { result += QString::fromLatin1( maybeResult.first, maybeResult.second ); return true; } return false; } bool parseAtom( const char * &scursor, const char * const send, QPair &result, bool allow8Bit ) { bool success = false; const char *start = scursor; while ( scursor != send ) { signed char ch = *scursor++; if ( ch > 0 && isAText( ch ) ) { // AText: OK success = true; } else if ( allow8Bit && ch < 0 ) { // 8bit char: not OK, but be tolerant. KMIME_WARN_8BIT( ch ); success = true; } else { // CTL or special - marking the end of the atom: // re-set sursor to point to the offending // char and return: scursor--; break; } } result.first = start; result.second = scursor - start; return success; } bool parseToken( const char * &scursor, const char * const send, QString &result, bool allow8Bit ) { QPair maybeResult; if ( parseToken( scursor, send, maybeResult, allow8Bit ) ) { result += QString::fromLatin1( maybeResult.first, maybeResult.second ); return true; } return false; } bool parseToken( const char * &scursor, const char * const send, QPair &result, bool allow8Bit ) { bool success = false; const char * start = scursor; while ( scursor != send ) { signed char ch = *scursor++; if ( ch > 0 && isTText( ch ) ) { // TText: OK success = true; } else if ( allow8Bit && ch < 0 ) { // 8bit char: not OK, but be tolerant. KMIME_WARN_8BIT( ch ); success = true; } else { // CTL or tspecial - marking the end of the atom: // re-set sursor to point to the offending // char and return: scursor--; break; } } result.first = start; result.second = scursor - start; return success; } #define READ_ch_OR_FAIL if ( scursor == send ) { \ KMIME_WARN_PREMATURE_END_OF( GenericQuotedString ); \ return false; \ } else { \ ch = *scursor++; \ } // known issues: // // - doesn't handle quoted CRLF bool parseGenericQuotedString( const char* &scursor, const char * const send, QString &result, bool isCRLF, const char openChar, const char closeChar ) { char ch; // We are in a quoted-string or domain-literal or comment and the // cursor points to the first char after the openChar. // We will apply unfolding and quoted-pair removal. // We return when we either encounter the end or unescaped openChar // or closeChar. assert( *(scursor-1) == openChar || *(scursor-1) == closeChar ); while ( scursor != send ) { ch = *scursor++; if ( ch == closeChar || ch == openChar ) { // end of quoted-string or another opening char: // let caller decide what to do. return true; } switch( ch ) { case '\\': // quoted-pair // misses "\" CRLF LWSP-char handling, see rfc822, 3.4.5 READ_ch_OR_FAIL; KMIME_WARN_IF_8BIT( ch ); result += QChar( ch ); break; case '\r': // ### // The case of lonely '\r' is easy to solve, as they're // not part of Unix Line-ending conventions. // But I see a problem if we are given Unix-native // line-ending-mails, where we cannot determine anymore // whether a given '\n' was part of a CRLF or was occurring // on it's own. READ_ch_OR_FAIL; if ( ch != '\n' ) { // CR on it's own... KMIME_WARN_LONE( CR ); result += QChar('\r'); scursor--; // points to after the '\r' again } else { // CRLF encountered. // lookahead: check for folding READ_ch_OR_FAIL; if ( ch == ' ' || ch == '\t' ) { // correct folding; // position cursor behind the CRLF WSP (unfolding) // and add the WSP to the result result += QChar( ch ); } else { // this is the "shouldn't happen"-case. There is a CRLF // inside a quoted-string without it being part of FWS. // We take it verbatim. KMIME_WARN_NON_FOLDING( CRLF ); result += "\r\n"; // the cursor is decremented again, so's we need not // duplicate the whole switch here. "ch" could've been // everything (incl. openChar or closeChar). scursor--; } } break; case '\n': // Note: CRLF has been handled above already! // ### LF needs special treatment, depending on whether isCRLF // is true (we can be sure a lonely '\n' was meant this way) or // false ('\n' alone could have meant LF or CRLF in the original // message. This parser assumes CRLF iff the LF is followed by // either WSP (folding) or NULL (premature end of quoted-string; // Should be fixed, since NULL is allowed as per rfc822). READ_ch_OR_FAIL; if ( !isCRLF && ( ch == ' ' || ch == '\t' ) ) { // folding // correct folding result += QChar( ch ); } else { // non-folding KMIME_WARN_LONE( LF ); result += QChar('\n'); // pos is decremented, so's we need not duplicate the whole // switch here. ch could've been everything (incl. <">, "\"). scursor--; } break; default: KMIME_WARN_IF_8BIT( ch ); result += QChar( ch ); } } return false; } // known issues: // // - doesn't handle encoded-word inside comments. bool parseComment( const char* &scursor, const char * const send, QString &result, bool isCRLF, bool reallySave ) { int commentNestingDepth = 1; const char *afterLastClosingParenPos = 0; QString maybeCmnt; const char *oldscursor = scursor; assert( *(scursor-1) == '(' ); while ( commentNestingDepth ) { QString cmntPart; if ( parseGenericQuotedString( scursor, send, cmntPart, isCRLF, '(', ')' ) ) { assert( *(scursor-1) == ')' || *(scursor-1) == '(' ); // see the kdoc for above function for the possible conditions // we have to check: switch ( *(scursor-1) ) { case ')': if ( reallySave ) { // add the chunk that's now surely inside the comment. result += maybeCmnt; result += cmntPart; if ( commentNestingDepth > 1 ) { // don't add the outermost ')'... result += QChar(')'); } maybeCmnt.clear(); } afterLastClosingParenPos = scursor; --commentNestingDepth; break; case '(': if ( reallySave ) { // don't add to "result" yet, because we might find that we // are already outside the (broken) comment... maybeCmnt += cmntPart; maybeCmnt += QChar('('); } ++commentNestingDepth; break; default: assert( 0 ); } // switch } else { // !parseGenericQuotedString, ie. premature end if ( afterLastClosingParenPos ) { scursor = afterLastClosingParenPos; } else { scursor = oldscursor; } return false; } } // while return true; } // known issues: none. bool parsePhrase( const char* &scursor, const char * const send, QString &result, bool isCRLF ) { enum { None, Phrase, Atom, EncodedWord, QuotedString } found = None; QString tmp; QByteArray lang, charset; const char *successfullyParsed = 0; // only used by the encoded-word branch const char *oldscursor; // used to suppress whitespace between adjacent encoded-words // (rfc2047, 6.2): bool lastWasEncodedWord = false; while ( scursor != send ) { char ch = *scursor++; switch ( ch ) { case '.': // broken, but allow for intorop's sake if ( found == None ) { --scursor; return false; } else { if ( scursor != send && ( *scursor == ' ' || *scursor == '\t' ) ) { result += ". "; } else { result += '.'; } successfullyParsed = scursor; } break; case '"': // quoted-string tmp.clear(); if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) { successfullyParsed = scursor; assert( *(scursor-1) == '"' ); switch ( found ) { case None: found = QuotedString; break; case Phrase: case Atom: case EncodedWord: case QuotedString: found = Phrase; result += QChar(' '); // rfc822, 3.4.4 break; default: assert( 0 ); } lastWasEncodedWord = false; result += tmp; } else { // premature end of quoted string. // What to do? Return leading '"' as special? Return as quoted-string? // We do the latter if we already found something, else signal failure. if ( found == None ) { return false; } else { result += QChar(' '); // rfc822, 3.4.4 result += tmp; return true; } } break; case '(': // comment // parse it, but ignore content: tmp.clear(); if ( parseComment( scursor, send, tmp, isCRLF, false /*don't bother with the content*/ ) ) { successfullyParsed = scursor; lastWasEncodedWord = false; // strictly interpreting rfc2047, 6.2 } else { if ( found == None ) { return false; } else { scursor = successfullyParsed; return true; } } break; case '=': // encoded-word tmp.clear(); oldscursor = scursor; lang.clear(); charset.clear(); if ( parseEncodedWord( scursor, send, tmp, lang, charset ) ) { successfullyParsed = scursor; switch ( found ) { case None: found = EncodedWord; break; case Phrase: case EncodedWord: case Atom: case QuotedString: if ( !lastWasEncodedWord ) { result += QChar(' '); // rfc822, 3.4.4 } found = Phrase; break; default: assert( 0 ); } lastWasEncodedWord = true; result += tmp; break; } else { // parse as atom: scursor = oldscursor; } // fall though... default: //atom tmp.clear(); scursor--; if ( parseAtom( scursor, send, tmp, true /* allow 8bit */ ) ) { successfullyParsed = scursor; switch ( found ) { case None: found = Atom; break; case Phrase: case Atom: case EncodedWord: case QuotedString: found = Phrase; result += QChar(' '); // rfc822, 3.4.4 break; default: assert( 0 ); } lastWasEncodedWord = false; result += tmp; } else { if ( found == None ) { return false; } else { scursor = successfullyParsed; return true; } } } eatWhiteSpace( scursor, send ); } return found != None; } bool parseDotAtom( const char* &scursor, const char * const send, QString &result, bool isCRLF ) { eatCFWS( scursor, send, isCRLF ); // always points to just after the last atom parsed: const char *successfullyParsed; QString tmp; if ( !parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) { return false; } result += tmp; successfullyParsed = scursor; while ( scursor != send ) { // end of header or no '.' -> return if ( scursor == send || *scursor != '.' ) { return true; } scursor++; // eat '.' if ( scursor == send || !isAText( *scursor ) ) { // end of header or no AText, but this time following a '.'!: // reset cursor to just after last successfully parsed char and // return: scursor = successfullyParsed; return true; } // try to parse the next atom: QString maybeAtom; if ( !parseAtom( scursor, send, maybeAtom, false /*no 8bit*/ ) ) { scursor = successfullyParsed; return true; } result += QChar('.'); result += maybeAtom; successfullyParsed = scursor; } scursor = successfullyParsed; return true; } void eatCFWS( const char* &scursor, const char * const send, bool isCRLF ) { QString dummy; while ( scursor != send ) { const char *oldscursor = scursor; char ch = *scursor++; switch( ch ) { case ' ': case '\t': // whitespace case '\r': case '\n': // folding continue; case '(': // comment if ( parseComment( scursor, send, dummy, isCRLF, false /*don't save*/ ) ) { continue; } scursor = oldscursor; return; default: scursor = oldscursor; return; } } } bool parseDomain( const char* &scursor, const char * const send, QString &result, bool isCRLF ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // domain := dot-atom / domain-literal / atom *("." atom) // // equivalent to: // domain = dot-atom / domain-literal, // since parseDotAtom does allow CFWS between atoms and dots if ( *scursor == '[' ) { // domain-literal: QString maybeDomainLiteral; // eat '[': scursor++; while ( parseGenericQuotedString( scursor, send, maybeDomainLiteral, isCRLF, '[', ']' ) ) { if ( scursor == send ) { // end of header: check for closing ']': if ( *(scursor-1) == ']' ) { // OK, last char was ']': result = maybeDomainLiteral; return true; } else { // not OK, domain-literal wasn't closed: return false; } } // we hit openChar in parseGenericQuotedString. // include it in maybeDomainLiteral and keep on parsing: if ( *(scursor-1) == '[' ) { maybeDomainLiteral += QChar('['); continue; } // OK, real end of domain-literal: result = maybeDomainLiteral; return true; } } else { // dot-atom: QString maybeDotAtom; if ( parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) { result = maybeDotAtom; return true; } } return false; } bool parseObsRoute( const char* &scursor, const char* const send, QStringList &result, bool isCRLF, bool save ) { while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // empty entry: if ( *scursor == ',' ) { scursor++; if ( save ) { result.append( QString() ); } continue; } // empty entry ending the list: if ( *scursor == ':' ) { scursor++; if ( save ) { result.append( QString() ); } return true; } // each non-empty entry must begin with '@': if ( *scursor != '@' ) { return false; } else { scursor++; } QString maybeDomain; if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) { return false; } if ( save ) { result.append( maybeDomain ); } // eat the following (optional) comma: eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( *scursor == ':' ) { scursor++; return true; } if ( *scursor == ',' ) { scursor++; } } return false; } bool parseAddrSpec( const char* &scursor, const char * const send, AddrSpec &result, bool isCRLF ) { // // STEP 1: // local-part := dot-atom / quoted-string / word *("." word) // // this is equivalent to: // local-part := word *("." word) QString maybeLocalPart; QString tmp; while ( scursor != send ) { // first, eat any whitespace eatCFWS( scursor, send, isCRLF ); char ch = *scursor++; switch ( ch ) { case '.': // dot maybeLocalPart += QChar('.'); break; case '@': goto SAW_AT_SIGN; break; case '"': // quoted-string tmp.clear(); if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) { maybeLocalPart += tmp; } else { return false; } break; default: // atom scursor--; // re-set scursor to point to ch again tmp.clear(); if ( parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) { maybeLocalPart += tmp; } else { return false; // parseAtom can only fail if the first char is non-atext. } break; } } return false; // // STEP 2: // domain // SAW_AT_SIGN: assert( *(scursor-1) == '@' ); QString maybeDomain; if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) { return false; } result.localPart = maybeLocalPart; result.domain = maybeDomain; return true; } bool parseAngleAddr( const char* &scursor, const char * const send, AddrSpec &result, bool isCRLF ) { // first, we need an opening angle bracket: eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '<' ) { return false; } scursor++; // eat '<' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( *scursor == '@' || *scursor == ',' ) { // obs-route: parse, but ignore: KMIME_WARN << "obsolete source route found! ignoring."; QStringList dummy; if ( !parseObsRoute( scursor, send, dummy, isCRLF, false /* don't save */ ) ) { return false; } // angle-addr isn't complete until after the '>': if ( scursor == send ) { return false; } } // parse addr-spec: AddrSpec maybeAddrSpec; if ( !parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '>' ) { return false; } scursor++; result = maybeAddrSpec; return true; } bool parseMailbox( const char* &scursor, const char * const send, Mailbox &result, bool isCRLF ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } AddrSpec maybeAddrSpec; QString maybeDisplayName; // first, try if it's a vanilla addr-spec: const char * oldscursor = scursor; if ( parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) { result.setAddress( maybeAddrSpec ); // check for the obsolete form of display-name (as comment): eatWhiteSpace( scursor, send ); if ( scursor != send && *scursor == '(' ) { scursor++; if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) { return false; } } result.setNameFrom7Bit( maybeDisplayName.toLatin1() ); return true; } scursor = oldscursor; // second, see if there's a display-name: if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) { // failed: reset cursor, note absent display-name maybeDisplayName.clear(); scursor = oldscursor; } else { // succeeded: eat CFWS eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } } // third, parse the angle-addr: if ( !parseAngleAddr( scursor, send, maybeAddrSpec, isCRLF ) ) { return false; } if ( maybeDisplayName.isNull() ) { // check for the obsolete form of display-name (as comment): eatWhiteSpace( scursor, send ); if ( scursor != send && *scursor == '(' ) { scursor++; if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) { return false; } } } result.setName( maybeDisplayName ); result.setAddress( maybeAddrSpec ); return true; } bool parseGroup( const char* &scursor, const char * const send, Address &result, bool isCRLF ) { // group := display-name ":" [ mailbox-list / CFWS ] ";" [CFWS] // // equivalent to: // group := display-name ":" [ obs-mbox-list ] ";" eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // get display-name: QString maybeDisplayName; if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) { return false; } // get ":": eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != ':' ) { return false; } result.displayName = maybeDisplayName; // get obs-mbox-list (may contain empty entries): scursor++; while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // empty entry: if ( *scursor == ',' ) { scursor++; continue; } // empty entry ending the list: if ( *scursor == ';' ) { scursor++; return true; } Mailbox maybeMailbox; if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { return false; } result.mailboxList.append( maybeMailbox ); eatCFWS( scursor, send, isCRLF ); // premature end: if ( scursor == send ) { return false; } // regular end of the list: if ( *scursor == ';' ) { scursor++; return true; } // eat regular list entry separator: if ( *scursor == ',' ) { scursor++; } } return false; } bool parseAddress( const char* &scursor, const char * const send, Address &result, bool isCRLF ) { // address := mailbox / group eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // first try if it's a single mailbox: Mailbox maybeMailbox; const char * oldscursor = scursor; if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { // yes, it is: result.displayName.clear(); result.mailboxList.append( maybeMailbox ); return true; } scursor = oldscursor; Address maybeAddress; // no, it's not a single mailbox. Try if it's a group: if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) ) { return false; } result = maybeAddress; return true; } bool parseAddressList( const char* &scursor, const char * const send, AddressList &result, bool isCRLF ) { while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // end of header: this is OK. if ( scursor == send ) { return true; } // empty entry: ignore: if ( *scursor == ',' ) { scursor++; continue; } // broken clients might use ';' as list delimiter, accept that as well if ( *scursor == ';' ) { scursor++; continue; } // parse one entry Address maybeAddress; if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) { return false; } result.append( maybeAddress ); eatCFWS( scursor, send, isCRLF ); // end of header: this is OK. if ( scursor == send ) { return true; } // comma separating entries: eat it. if ( *scursor == ',' ) { scursor++; } } return true; } static QString asterisk = QString::fromLatin1( "*0*", 1 ); static QString asteriskZero = QString::fromLatin1( "*0*", 2 ); //static QString asteriskZeroAsterisk = QString::fromLatin1( "*0*", 3 ); bool parseParameter( const char* &scursor, const char * const send, QPair &result, bool isCRLF ) { // parameter = regular-parameter / extended-parameter // regular-parameter = regular-parameter-name "=" value // extended-parameter = // value = token / quoted-string // // note that rfc2231 handling is out of the scope of this function. // Therefore we return the attribute as QString and the value as // (start,length) tupel if we see that the value is encoded // (trailing asterisk), for parseParameterList to decode... eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // parse the parameter name: // QString maybeAttribute; if ( !parseToken( scursor, send, maybeAttribute, false /* no 8bit */ ) ) { return false; } eatCFWS( scursor, send, isCRLF ); // premature end: not OK (haven't seen '=' yet). if ( scursor == send || *scursor != '=' ) { return false; } scursor++; // eat '=' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { // don't choke on attribute=, meaning the value was omitted: if ( maybeAttribute.endsWith( asterisk ) ) { KMIME_WARN << "attribute ends with \"*\", but value is empty!" "Chopping away \"*\"."; maybeAttribute.truncate( maybeAttribute.length() - 1 ); } result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() ); return true; } const char * oldscursor = scursor; // // parse the parameter value: // QStringOrQPair maybeValue; if ( *scursor == '"' ) { // value is a quoted-string: scursor++; if ( maybeAttribute.endsWith( asterisk ) ) { // attributes ending with "*" designate extended-parameters, // which cannot have quoted-strings as values. So we remove the // trailing "*" to not confuse upper layers. KMIME_WARN << "attribute ends with \"*\", but value is a quoted-string!" "Chopping away \"*\"."; maybeAttribute.truncate( maybeAttribute.length() - 1 ); } if ( !parseGenericQuotedString( scursor, send, maybeValue.qstring, isCRLF ) ) { scursor = oldscursor; result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() ); return false; // this case needs further processing by upper layers!! } } else { // value is a token: if ( !parseToken( scursor, send, maybeValue.qpair, false /* no 8bit */ ) ) { scursor = oldscursor; result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() ); return false; // this case needs further processing by upper layers!! } } result = qMakePair( maybeAttribute.toLower(), maybeValue ); return true; } bool parseRawParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF ) { // we use parseParameter() consecutively to obtain a map of raw // attributes to raw values. "Raw" here means that we don't do // rfc2231 decoding and concatenation. This is left to // parseParameterList(), which will call this function. // // The main reason for making this chunk of code a separate // (private) method is that we can deal with broken parameters // _here_ and leave the rfc2231 handling solely to // parseParameterList(), which will still be enough work. while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // empty entry ending the list: OK. if ( scursor == send ) { return true; } // empty list entry: ignore. if ( *scursor == ';' ) { scursor++; continue; } QPair maybeParameter; if ( !parseParameter( scursor, send, maybeParameter, isCRLF ) ) { // we need to do a bit of work if the attribute is not // NULL. These are the cases marked with "needs further // processing" in parseParameter(). Specifically, parsing of the // token or the quoted-string, which should represent the value, // failed. We take the easy way out and simply search for the // next ';' to start parsing again. (Another option would be to // take the text between '=' and ';' as value) if ( maybeParameter.first.isNull() ) { return false; } while ( scursor != send ) { if ( *scursor++ == ';' ) { goto IS_SEMICOLON; } } // scursor == send case: end of list. return true; IS_SEMICOLON: // *scursor == ';' case: parse next entry. continue; } // successful parsing brings us here: result.insert( maybeParameter.first, maybeParameter.second ); eatCFWS( scursor, send, isCRLF ); // end of header: ends list. if ( scursor == send ) { return true; } // regular separator: eat it. if ( *scursor == ';' ) { scursor++; } } return true; } static void decodeRFC2231Value( Codec* &rfc2231Codec, QTextCodec* &textcodec, bool isContinuation, QString &value, QPair &source ) { // // parse the raw value into (charset,language,text): // const char * decBegin = source.first; const char * decCursor = decBegin; const char * decEnd = decCursor + source.second; if ( !isContinuation ) { // find the first single quote while ( decCursor != decEnd ) { if ( *decCursor == '\'' ) { break; } else { decCursor++; } } if ( decCursor == decEnd ) { // there wasn't a single single quote at all! // take the whole value to be in latin-1: KMIME_WARN << "No charset in extended-initial-value." "Assuming \"iso-8859-1\"."; value += QString::fromLatin1( decBegin, source.second ); return; } QByteArray charset( decBegin, decCursor - decBegin ); const char * oldDecCursor = ++decCursor; // find the second single quote (we ignore the language tag): while ( decCursor != decEnd ) { if ( *decCursor == '\'' ) { break; } else { decCursor++; } } if ( decCursor == decEnd ) { KMIME_WARN << "No language in extended-initial-value." "Trying to recover."; decCursor = oldDecCursor; } else { decCursor++; } // decCursor now points to the start of the // "extended-other-values": // // get the decoders: // bool matchOK = false; textcodec = KGlobal::charsets()->codecForName( charset, matchOK ); if ( !matchOK ) { textcodec = 0; KMIME_WARN_UNKNOWN( Charset, charset ); } } if ( !rfc2231Codec ) { rfc2231Codec = Codec::codecForName("x-kmime-rfc2231"); assert( rfc2231Codec ); } if ( !textcodec ) { value += QString::fromLatin1( decCursor, decEnd - decCursor ); return; } Decoder * dec = rfc2231Codec->makeDecoder(); assert( dec ); // // do the decoding: // QByteArray buffer; buffer.resize( rfc2231Codec->maxDecodedSizeFor( decEnd - decCursor ) ); QByteArray::Iterator bit = buffer.begin(); QByteArray::ConstIterator bend = buffer.end(); if ( !dec->decode( decCursor, decEnd, bit, bend ) ) { KMIME_WARN << rfc2231Codec->name() << "codec lies about its maxDecodedSizeFor()" << endl << "result may be truncated"; } value += textcodec->toUnicode( buffer.begin(), bit - buffer.begin() ); // kDebug(5320) << "value now: \"" << value << "\""; // cleanup: delete dec; } // known issues: // - permutes rfc2231 continuations when the total number of parts // exceeds 10 (other-sections then becomes *xy, ie. two digits) bool parseParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF ) { // parse the list into raw attribute-value pairs: QMap rawParameterList; if (!parseRawParameterList( scursor, send, rawParameterList, isCRLF ) ) { return false; } if ( rawParameterList.isEmpty() ) { return true; } // decode rfc 2231 continuations and alternate charset encoding: // NOTE: this code assumes that what QMapIterator delivers is sorted // by the key! Codec * rfc2231Codec = 0; QTextCodec * textcodec = 0; QString attribute; QString value; enum Modes { NoMode = 0x0, Continued = 0x1, Encoded = 0x2 } mode; QMap::Iterator it, end = rawParameterList.end(); for ( it = rawParameterList.begin() ; it != end ; ++it ) { if ( attribute.isNull() || !it.key().startsWith( attribute ) ) { // // new attribute: // // store the last attribute/value pair in the result map now: if ( !attribute.isNull() ) { result.insert( attribute, value ); } // and extract the information from the new raw attribute: value.clear(); attribute = it.key(); mode = NoMode; // is the value encoded? if ( attribute.endsWith( asterisk ) ) { attribute.truncate( attribute.length() - 1 ); mode = (Modes) ((int) mode | Encoded); } // is the value continued? if ( attribute.endsWith( asteriskZero ) ) { attribute.truncate( attribute.length() - 2 ); mode = (Modes) ((int) mode | Continued); } // // decode if necessary: // if ( mode & Encoded ) { decodeRFC2231Value( rfc2231Codec, textcodec, false, /* isn't continuation */ value, (*it).qpair ); } else { // not encoded. if ( (*it).qpair.first ) { value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second ); } else { value += (*it).qstring; } } // // shortcut-processing when the value isn't encoded: // if ( !(mode & Continued) ) { // save result already: result.insert( attribute, value ); // force begin of a new attribute: attribute.clear(); } } else { // it.key().startsWith( attribute ) // // continuation // // ignore the section and trust QMap to have sorted the keys: if ( it.key().endsWith( asterisk ) ) { // encoded decodeRFC2231Value( rfc2231Codec, textcodec, true, /* is continuation */ value, (*it).qpair ); } else { // not encoded if ( (*it).qpair.first ) { value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second ); } else { value += (*it).qstring; } } } } // write last attr/value pair: if ( !attribute.isNull() ) { result.insert( attribute, value ); } return true; } static const char * const stdDayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const int stdDayNamesLen = sizeof stdDayNames / sizeof *stdDayNames; static bool parseDayName( const char* &scursor, const char * const send ) { // check bounds: if ( send - scursor < 3 ) { return false; } for ( int i = 0 ; i < stdDayNamesLen ; ++i ) { if ( qstrnicmp( scursor, stdDayNames[i], 3 ) == 0 ) { scursor += 3; // kDebug(5320) << "found" << stdDayNames[i]; return true; } } return false; } static const char * const stdMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const int stdMonthNamesLen = sizeof stdMonthNames / sizeof *stdMonthNames; static bool parseMonthName( const char* &scursor, const char * const send, int &result ) { // check bounds: if ( send - scursor < 3 ) { return false; } for ( result = 0 ; result < stdMonthNamesLen ; ++result ) { if ( qstrnicmp( scursor, stdMonthNames[result], 3 ) == 0 ) { scursor += 3; return true; } } // not found: return false; } static const struct { const char * tzName; long int secsEastOfGMT; } timeZones[] = { // rfc 822 timezones: { "GMT", 0 }, { "UT", 0 }, { "EDT", -4*3600 }, { "EST", -5*3600 }, { "MST", -5*3600 }, { "CST", -6*3600 }, { "MDT", -6*3600 }, { "MST", -7*3600 }, { "PDT", -7*3600 }, { "PST", -8*3600 }, // common, non-rfc-822 zones: { "CET", 1*3600 }, { "MET", 1*3600 }, { "UTC", 0 }, { "CEST", 2*3600 }, { "BST", 1*3600 }, // rfc 822 military timezones: { "Z", 0 }, { "A", -1*3600 }, { "B", -2*3600 }, { "C", -3*3600 }, { "D", -4*3600 }, { "E", -5*3600 }, { "F", -6*3600 }, { "G", -7*3600 }, { "H", -8*3600 }, { "I", -9*3600 }, // J is not used! { "K", -10*3600 }, { "L", -11*3600 }, { "M", -12*3600 }, { "N", 1*3600 }, { "O", 2*3600 }, { "P", 3*3600 }, { "Q", 4*3600 }, { "R", 5*3600 }, { "S", 6*3600 }, { "T", 7*3600 }, { "U", 8*3600 }, { "V", 9*3600 }, { "W", 10*3600 }, { "X", 11*3600 }, { "Y", 12*3600 }, }; static const int timeZonesLen = sizeof timeZones / sizeof *timeZones; static bool parseAlphaNumericTimeZone( const char* &scursor, const char * const send, long int &secsEastOfGMT, bool &timeZoneKnown ) { QPair maybeTimeZone( 0, 0 ); if ( !parseToken( scursor, send, maybeTimeZone, false /*no 8bit*/ ) ) { return false; } for ( int i = 0 ; i < timeZonesLen ; ++i ) { if ( qstrnicmp( timeZones[i].tzName, maybeTimeZone.first, maybeTimeZone.second ) == 0 ) { scursor += maybeTimeZone.second; secsEastOfGMT = timeZones[i].secsEastOfGMT; timeZoneKnown = true; return true; } } // don't choke just because we don't happen to know the time zone KMIME_WARN_UNKNOWN( time zone, QByteArray( maybeTimeZone.first, maybeTimeZone.second ) ); secsEastOfGMT = 0; timeZoneKnown = false; return true; } // parse a number and return the number of digits parsed: int parseDigits( const char* &scursor, const char * const send, int &result ) { result = 0; int digits = 0; for ( ; scursor != send && isdigit( *scursor ) ; scursor++, digits++ ) { result *= 10; result += int( *scursor - '0' ); } return digits; } static bool parseTimeOfDay( const char* &scursor, const char * const send, int &hour, int &min, int &sec, bool isCRLF=false ) { // time-of-day := 2DIGIT [CFWS] ":" [CFWS] 2DIGIT [ [CFWS] ":" 2DIGIT ] // // 2DIGIT representing "hour": // if ( !parseDigits( scursor, send, hour ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != ':' ) { return false; } scursor++; // eat ':' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // 2DIGIT representing "minute": // if ( !parseDigits( scursor, send, min ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return true; // seconds are optional } // // let's see if we have a 2DIGIT representing "second": // if ( *scursor == ':' ) { // yepp, there are seconds: scursor++; // eat ':' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( !parseDigits( scursor, send, sec ) ) { return false; } } else { sec = 0; } return true; } bool parseTime( const char* &scursor, const char * send, int &hour, int &min, int &sec, long int &secsEastOfGMT, bool &timeZoneKnown, bool isCRLF ) { // time := time-of-day CFWS ( zone / obs-zone ) // // obs-zone := "UT" / "GMT" / // "EST" / "EDT" / ; -0500 / -0400 // "CST" / "CDT" / ; -0600 / -0500 // "MST" / "MDT" / ; -0700 / -0600 // "PST" / "PDT" / ; -0800 / -0700 // "A"-"I" / "a"-"i" / // "K"-"Z" / "k"-"z" eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( !parseTimeOfDay( scursor, send, hour, min, sec, isCRLF ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { timeZoneKnown = false; secsEastOfGMT = 0; return true; // allow missing timezone } timeZoneKnown = true; if ( *scursor == '+' || *scursor == '-' ) { // remember and eat '-'/'+': const char sign = *scursor++; // numerical timezone: int maybeTimeZone; if ( parseDigits( scursor, send, maybeTimeZone ) != 4 ) { return false; } secsEastOfGMT = 60 * ( maybeTimeZone / 100 * 60 + maybeTimeZone % 100 ); if ( sign == '-' ) { secsEastOfGMT *= -1; if ( secsEastOfGMT == 0 ) { timeZoneKnown = false; // -0000 means indetermined tz } } } else { // maybe alphanumeric timezone: if ( !parseAlphaNumericTimeZone( scursor, send, secsEastOfGMT, timeZoneKnown ) ) { return false; } } return true; } bool parseDateTime( const char* &scursor, const char * const send, KDateTime &result, bool isCRLF ) { // Parsing date-time; strict mode: // // date-time := [ [CFWS] day-name [CFWS] "," ] ; wday // (expanded) [CFWS] 1*2DIGIT CFWS month-name CFWS 2*DIGIT [CFWS] ; date // time // // day-name := "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // month-name := "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" result = KDateTime(); QDateTime maybeDateTime; eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // let's see if there's a day-of-week: // if ( parseDayName( scursor, send ) ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // day-name should be followed by ',' but we treat it as optional: if ( *scursor == ',' ) { scursor++; // eat ',' eatCFWS( scursor, send, isCRLF ); } } // // 1*2DIGIT representing "day" (of month): // int maybeDay; if ( !parseDigits( scursor, send, maybeDay ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // month-name: // int maybeMonth = 0; if ( !parseMonthName( scursor, send, maybeMonth ) ) { return false; } if ( scursor == send ) { return false; } assert( maybeMonth >= 0 ); assert( maybeMonth <= 11 ); ++maybeMonth; // 0-11 -> 1-12 eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // 2*DIGIT representing "year": // int maybeYear; if ( !parseDigits( scursor, send, maybeYear ) ) { return false; } // RFC 2822 4.3 processing: if ( maybeYear < 50 ) { maybeYear += 2000; } else if ( maybeYear < 1000 ) { maybeYear += 1900; } // else keep as is if ( maybeYear < 1900 ) { return false; // rfc2822, 3.3 } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } maybeDateTime.setDate( QDate( maybeYear, maybeMonth, maybeDay ) ); // // time // int maybeHour, maybeMinute, maybeSecond; long int secsEastOfGMT; bool timeZoneKnown = true; if ( !parseTime( scursor, send, maybeHour, maybeMinute, maybeSecond, secsEastOfGMT, timeZoneKnown, isCRLF ) ) { return false; } maybeDateTime.setTime( QTime( maybeHour, maybeMinute, maybeSecond ) ); if ( !maybeDateTime.isValid() ) return false; result = KDateTime( maybeDateTime, KDateTime::Spec( KDateTime::OffsetFromUTC, secsEastOfGMT ) ); if ( !result.isValid() ) return false; return true; } Headers::Base *extractFirstHeader( QByteArray &head ) { int pos1=-1, pos2=0, len=head.length()-1; bool folded( false ); Headers::Base *header=0; pos1 = head.indexOf( ": " ); if ( pos1 > -1 ) { //there is another header pos2 = pos1 += 2; //skip the name if ( head[pos2] != '\n' ) { // check if the header is not empty while ( 1 ) { pos2 = head.indexOf( '\n', pos2 + 1 ); if ( pos2 == -1 || pos2 == len || ( head[pos2+1] != ' ' && head[pos2+1] != '\t' ) ) { //break if we reach the end of the string, honor folded lines break; } else { folded = true; } } } if ( pos2 < 0 ) { pos2 = len + 1; //take the rest of the string } QByteArray rawType = head.left( pos1 - 2 ); QByteArray rawData = head.mid( pos1, pos2 - pos1 ); if( folded ) { rawData = unfoldHeader( rawData ); } header = HeaderFactory::self()->createHeader( rawType ); if( !header ) { kWarning() << "Returning Generic header of type" << rawType; header = new Headers::Generic( rawType ); } header->from7BitString( rawData ); head.remove( 0, pos2 + 1 ); } else { head = ""; } return header; } Headers::Base::List parseHeaders( const QByteArray &head ) { Headers::Base::List ret; Headers::Base *h; QByteArray copy = head; while( ( h = extractFirstHeader( copy ) ) ) { ret << h; } return ret; } } // namespace HeaderParsing } // namespace KMime diff --git a/kmime/kmime_header_parsing.h b/kmime/kmime_header_parsing.h index 3dca2d1a8..88cb00cc8 100644 --- a/kmime/kmime_header_parsing.h +++ b/kmime/kmime_header_parsing.h @@ -1,343 +1,343 @@ /* -*- c++ -*- kmime_header_parsing.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz 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 __KMIME_HEADER_PARSING_H__ #define __KMIME_HEADER_PARSING_H__ #include #include #include #include "kmime_export.h" template class QMap; class QStringList; namespace KMime { namespace Headers { class Base; } namespace Types { // for when we can't make up our mind what to use... struct KMIME_EXPORT QStringOrQPair { QStringOrQPair() : qstring(), qpair( 0, 0 ) {} QString qstring; QPair qpair; }; struct KMIME_EXPORT AddrSpec { QString asString() const; /*! This is the same as asString(), except it decodes IDNs for display */ QString asPrettyString() const; bool isEmpty() const; QString localPart; QString domain; }; typedef QList AddrSpecList; /** Represents an (email address, display name) pair according RFC 2822, section 3.4. */ class KMIME_EXPORT Mailbox { public: typedef QList List; /** Returns a string representation of the email address, without the angle brackets. */ QByteArray address() const; AddrSpec addrSpec() const; /** Returns the display name. */ QString name() const; /** Sets the email address. */ void setAddress( const AddrSpec &addr ); /** Sets the email address. */ void setAddress( const QByteArray &addr ); /** Sets the name. */ void setName( const QString &name ); /** Sets the name based on a 7bit encoded string. */ void setNameFrom7Bit( const QByteArray &name, const QByteArray &defaultCharset = QByteArray() ); /** Returns true if this mailbox has an address. */ bool hasAddress() const; /** Returns true if this mailbox has a display name. */ bool hasName() const; /** Returns a assembled display name / address string of the following form: "Display Name <address>". These are unicode strings without any transport encoding, ie. they are only suitable for displaying. */ QString prettyAddress() const; /** Parses the given unicode string. */ void fromUnicodeString( const QString &s ); /** Parses the given 7bit encoded string. */ void from7BitString( const QByteArray &s ); /** Returns a 7bit transport encoded representation of this mailbox. @param encCharset The charset used for encoding. */ QByteArray as7BitString( const QByteArray &encCharset ) const; private: QString mDisplayName; AddrSpec mAddrSpec; }; typedef QList MailboxList; struct KMIME_EXPORT Address { QString displayName; MailboxList mailboxList; }; typedef QList
      AddressList; } // namespace KMime::Types namespace HeaderParsing { /** Parses the encoded word. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param result the decoded string the encoded work represented. @param language The language parameter according to RFC 2231, section 5. @param usedCS the used charset is returned here @param defaultCS the charset to use in case the detected one isn't known to us. @param forceCS force the use of the default charset. @return true if the input string was successfully decode; false otherwise. */ KMIME_EXPORT bool parseEncodedWord( const char* &scursor, const char * const send, QString &result, QByteArray &language, QByteArray &usedCS, const QByteArray &defaultCS = QByteArray(), bool forceCS = false ); // // The parsing squad: // /** You may or may not have already started parsing into the atom. This function will go on where you left off. */ KMIME_EXPORT bool parseAtom( const char* &scursor, const char * const send, QString &result, bool allow8Bit=false ); KMIME_EXPORT bool parseAtom( const char* &scursor, const char * const send, QPair &result, bool allow8Bit=false ); /** You may or may not have already started parsing into the token. This function will go on where you left off. */ KMIME_EXPORT bool parseToken( const char* &scursor, const char * const send, QString &result, bool allow8Bit=false ); KMIME_EXPORT bool parseToken( const char* &scursor, const char * const send, QPair &result, bool allow8Bit=false ); /** @p scursor must be positioned after the opening openChar. */ KMIME_EXPORT bool parseGenericQuotedString( const char* &scursor, const char* const send, QString &result, bool isCRLF, const char openChar='"', const char closeChar='"' ); /** @p scursor must be positioned right after the opening '(' */ KMIME_EXPORT bool parseComment( const char* &scursor, const char * const send, QString &result, bool isCRLF=false, bool reallySave=true ); /** Parses a phrase. You may or may not have already started parsing into the phrase, but only if it starts with atext. If you setup this function to parse a phrase starting with an encoded-word or quoted-string, @p scursor has to point to the char introducing the encoded-word or quoted-string, resp. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param result the parsed string. @return true if the input phrase was successfully parsed; false otherwise. */ KMIME_EXPORT bool parsePhrase( const char* &scursor, const char * const send, QString &result, bool isCRLF=false ); /** Parses into the initial atom. You may or may not have already started parsing into the initial atom, but not up to it's end. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param result the parsed string. @return true if the input phrase was successfully parsed; false otherwise. */ KMIME_EXPORT bool parseDotAtom( const char* &scursor, const char * const send, QString &result, bool isCRLF=false ); /** Eats comment-folding-white-space, skips whitespace, folding and comments (even nested ones) and stops at the next non-CFWS character. After calling this function, you should check whether @p scursor == @p send (end of header reached). If a comment with unbalanced parantheses is encountered, @p scursor is being positioned on the opening '(' of the outmost comment. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param isCRLF true if input string is terminated with a CRLF. */ KMIME_EXPORT void eatCFWS( const char* &scursor, const char * const send, bool isCRLF ); KMIME_EXPORT bool parseDomain( const char* &scursor, const char * const send, QString &result, bool isCRLF=false ); KMIME_EXPORT bool parseObsRoute( const char* &scursor, const char * const send, QStringList &result, bool isCRLF=false, bool save=false ); KMIME_EXPORT bool parseAddrSpec( const char* &scursor, const char * const send, Types::AddrSpec &result, bool isCRLF=false ); KMIME_EXPORT bool parseAngleAddr( const char* &scursor, const char * const send, Types::AddrSpec &result, bool isCRLF=false ); /** Parses a single mailbox. RFC 2822, section 3.4 defines a mailbox as follows:
      mailbox := addr-spec / ([ display-name ] angle-addr)
      KMime also accepts the legacy format of specifying display names:
      mailbox := (addr-spec [ "(" display-name ")" ])
         / ([ display-name ] angle-addr)
         / (angle-addr "(" display-name ")")
      @param scursor pointer to the first character of the input string @param send pointer to end of input buffer @param result the parsing result @param isCRLF true if input string is terminated with a CRLF. */ KMIME_EXPORT bool parseMailbox( const char* &scursor, const char * const send, Types::Mailbox &result, bool isCRLF=false ); KMIME_EXPORT bool parseGroup( const char* &scursor, const char * const send, Types::Address &result, bool isCRLF=false ); KMIME_EXPORT bool parseAddress( const char* &scursor, const char * const send, Types::Address &result, bool isCRLF=false ); KMIME_EXPORT bool parseAddressList( const char* &scursor, const char * const send, Types::AddressList &result, bool isCRLF=false ); KMIME_EXPORT bool parseParameter( const char* &scursor, const char * const send, QPair &result, bool isCRLF=false ); KMIME_EXPORT bool parseParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF=false ); KMIME_EXPORT bool parseRawParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF=false ); /** Parses an integer number. @param scursor pointer to the first character of the input string @param send pointer to end of input buffer @param result the parsing result @returns The number of parsed digits (don't confuse with @p result!) */ KMIME_EXPORT int parseDigits( const char* &scursor, const char* const send, int &result ); KMIME_EXPORT bool parseTime( const char* &scursor, const char * const send, int &hour, int &min, int &sec, long int &secsEastOfGMT, bool &timeZoneKnown, bool isCRLF=false ); KMIME_EXPORT bool parseDateTime( const char* &scursor, const char * const send, KDateTime &result, bool isCRLF=false ); KMIME_EXPORT KMime::Headers::Base *extractFirstHeader( QByteArray &head ); KMIME_EXPORT QList parseHeaders( const QByteArray &head ); } // namespace HeaderParsing } // namespace KMime #endif // __KMIME_HEADER_PARSING_H__ diff --git a/kmime/kmime_headerfactory.cpp b/kmime/kmime_headerfactory.cpp index 86343f786..e21a3dda8 100644 --- a/kmime/kmime_headerfactory.cpp +++ b/kmime/kmime_headerfactory.cpp @@ -1,117 +1,117 @@ /* kmime_header_factory.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2009 Constantin Berzan 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 API for handling MIME data and defines the HeaderFactory class. @brief Defines the HeaderFactory class. @authors Constantin Berzan \ */ #include "kmime_headerfactory.h" #include "kmime_headers.h" #include #include #include using namespace KMime; /** * @internal * Private class that helps to provide binary compatibility between releases. */ class KMime::HeaderFactoryPrivate { public: HeaderFactoryPrivate(); ~HeaderFactoryPrivate(); HeaderFactory *const instance; QHash headerMakers; // Type->obj mapping; with lower-case type. }; K_GLOBAL_STATIC( HeaderFactoryPrivate, sInstance ) HeaderFactoryPrivate::HeaderFactoryPrivate() : instance( new HeaderFactory( this ) ) { } HeaderFactoryPrivate::~HeaderFactoryPrivate() { qDeleteAll( headerMakers.values() ); delete instance; } HeaderFactory* HeaderFactory::self() { return sInstance->instance; } Headers::Base *HeaderFactory::createHeader( const QByteArray &type ) { Q_ASSERT( !type.isEmpty() ); const HeaderMakerBase *maker = d->headerMakers.value( type.toLower() ); if( maker ) { return maker->create(); } else { kError() << "Unknown header type" << type; //return new Headers::Generic; return 0; } } HeaderFactory::HeaderFactory( HeaderFactoryPrivate *dd ) : d( dd ) { } HeaderFactory::~HeaderFactory() { } bool HeaderFactory::registerHeaderMaker( const QByteArray &type, HeaderMakerBase *maker ) { if( type.isEmpty() ) { // This is probably a generic (but not abstract) header, // like Address or MailboxList. We cannot register those. kWarning() << "Tried to register header with empty type."; return false; } const QByteArray ltype = type.toLower(); if( d->headerMakers.contains( ltype ) ) { kWarning() << "Header of type" << type << "already registered."; // TODO should we make this an error? return false; } d->headerMakers.insert( ltype, maker ); return true; } diff --git a/kmime/kmime_headerfactory.h b/kmime/kmime_headerfactory.h index 640a62f77..4889963ea 100644 --- a/kmime/kmime_headerfactory.h +++ b/kmime/kmime_headerfactory.h @@ -1,95 +1,95 @@ /* kmime_header_factory.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2009 Constantin Berzan 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 API for handling @ref MIME data and defines the HeaderFactory class. @brief Defines the HeaderFactory class. @authors Constantin Berzan \ */ #ifndef __KMIME_HEADERFACTORY_H__ #define __KMIME_HEADERFACTORY_H__ #include "kmime_export.h" #include namespace KMime { namespace Headers { class Base; } class HeaderMakerBase { public: virtual ~HeaderMakerBase() {} virtual Headers::Base *create() const = 0; }; template class HeaderMaker : public HeaderMakerBase { public: virtual Headers::Base *create() const { return new T; } }; class HeaderFactoryPrivate; /** docu TODO */ class KMIME_EXPORT HeaderFactory { public: static HeaderFactory* self(); template inline bool registerHeader() { T dummy; return registerHeaderMaker( QByteArray( dummy.type() ), new HeaderMaker() ); } Headers::Base *createHeader( const QByteArray &type ); private: HeaderFactory( HeaderFactoryPrivate *dd ); HeaderFactory( const HeaderFactory &other ); // undefined HeaderFactory& operator=( const HeaderFactory &other ); // undefined ~HeaderFactory(); bool registerHeaderMaker( const QByteArray &type, HeaderMakerBase *maker ); friend class HeaderFactoryPrivate; HeaderFactoryPrivate *const d; }; } // namespace KMime #endif /* __KMIME_HEADERFACTORY_H__ */ diff --git a/kmime/kmime_headers.cpp b/kmime/kmime_headers.cpp index 637d6e695..5d2f5dfbb 100644 --- a/kmime/kmime_headers.cpp +++ b/kmime/kmime_headers.cpp @@ -1,2180 +1,2180 @@ /* -*- c++ -*- kmime_headers.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the various header classes: - header's base class defining the common interface - generic base classes for different types of fields - incompatible, Structured-based field classes - compatible, Unstructured-based field classes @brief Defines the various headers classes. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #include "kmime_headers.h" #include "kmime_headers_p.h" #include "kmime_util.h" #include "kmime_content.h" #include "kmime_codecs.h" #include "kmime_header_parsing.h" #include "kmime_headerfactory.h" #include "kmime_warning.h" #include #include #include #include #include #include #include template bool registerHeaderHelper() { const T dummy; if( QByteArray( dummy.type() ).isEmpty() ) { // This is a generic header. return false; } return KMime::HeaderFactory::self()->registerHeader(); } // macro to register a header with HeaderFactory #define kmime_register_header( subclass ) \ namespace { const bool dummyForRegistering##subclass = registerHeaderHelper(); } // macro to generate a default constructor implementation #define kmime_mk_trivial_ctor( subclass, baseclass ) \ subclass::subclass( Content *parent ) : baseclass( parent ) \ { \ clear(); \ } \ \ subclass::subclass( Content *parent, const QByteArray &s ) : baseclass( parent ) \ { \ from7BitString( s ); \ } \ \ subclass::subclass( Content *parent, const QString &s, const QByteArray &charset ) : \ baseclass( parent ) \ { \ fromUnicodeString( s, charset ); \ } \ \ subclass::~subclass() {} \ \ kmime_register_header( subclass ) // end kmime_mk_trivial_ctor #define kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ subclass::subclass( Content *parent ) : baseclass( new subclass##Private, parent ) \ { \ clear(); \ } \ \ subclass::subclass( Content *parent, const QByteArray &s ) : baseclass( new subclass##Private, parent ) \ { \ from7BitString( s ); \ } \ \ subclass::subclass( Content *parent, const QString &s, const QByteArray &charset ) : \ baseclass( new subclass##Private, parent ) \ { \ fromUnicodeString( s, charset ); \ } \ \ subclass::~subclass() {} \ \ kmime_register_header( subclass ) // end kmime_mk_trivial_ctor_with_dptr #define kmime_mk_trivial_ctor_with_name( subclass, baseclass, name ) \ kmime_mk_trivial_ctor( subclass, baseclass ) \ \ const char *subclass::type() const \ { \ return #name; \ } #define kmime_mk_trivial_ctor_with_name_and_dptr( subclass, baseclass, name ) \ kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ const char *subclass::type() const { return #name; } #define kmime_mk_dptr_ctor( subclass, baseclass ) \ subclass::subclass( subclass##Private *d, KMime::Content *parent ) : baseclass( d, parent ) {} using namespace KMime; using namespace KMime::Headers; using namespace KMime::Types; using namespace KMime::HeaderParsing; namespace KMime { namespace Headers { //--------------------------------------- Base::Base( KMime::Content *parent ) : d_ptr( new BasePrivate ) { Q_D(Base); d->parent = parent; } Base::Base( BasePrivate *dd, KMime::Content *parent ) : d_ptr( dd ) { Q_D(Base); d->parent = parent; } Base::~Base() { delete d_ptr; d_ptr = 0; } KMime::Content *Base::parent() const { return d_ptr->parent; } void Base::setParent( KMime::Content *parent ) { d_ptr->parent = parent; } QByteArray Base::rfc2047Charset() const { if ( d_ptr->encCS.isEmpty() || forceDefaultCharset() ) { return defaultCharset(); } else { return d_ptr->encCS; } } void Base::setRFC2047Charset( const QByteArray &cs ) { d_ptr->encCS = cachedCharset( cs ); } bool Base::forceDefaultCharset() const { return ( parent() != 0 ? parent()->forceDefaultCharset() : false ); } QByteArray Base::defaultCharset() const { return ( parent() != 0 ? parent()->defaultCharset() : Latin1 ); } const char *Base::type() const { return ""; } bool Base::is( const char *t ) const { return strcasecmp( t, type() ) == 0; } bool Base::isMimeHeader() const { return strncasecmp( type(), "Content-", 8 ) == 0; } bool Base::isXHeader() const { return strncmp( type(), "X-", 2 ) == 0; } QByteArray Base::typeIntro() const { return QByteArray( type() ) + ": "; } //-------------------------------------- namespace Generics { //------------------------------ //@cond PRIVATE kmime_mk_dptr_ctor( Unstructured, Base ) //@endcond Unstructured::Unstructured( Content *p ) : Base( new UnstructuredPrivate, p ) { } Unstructured::Unstructured( Content *p, const QByteArray &s ) : Base( new UnstructuredPrivate, p ) { from7BitString( s ); } Unstructured::Unstructured( Content *p, const QString &s, const QByteArray &cs ) : Base( new UnstructuredPrivate, p ) { fromUnicodeString( s, cs ); } Unstructured::~Unstructured() { } void Unstructured::from7BitString( const QByteArray &s ) { Q_D(Unstructured); d->decoded = decodeRFC2047String( s, d->encCS, defaultCharset(), forceDefaultCharset() ); } QByteArray Unstructured::as7BitString( bool withHeaderType ) const { const Q_D(Unstructured); QByteArray result; if ( withHeaderType ) { result = typeIntro(); } result += encodeRFC2047String( d->decoded, d->encCS ) ; return result; } void Unstructured::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(Unstructured); d->decoded = s; d->encCS = cachedCharset( b ); } QString Unstructured::asUnicodeString() const { return d_func()->decoded; } void Unstructured::clear() { Q_D(Unstructured); d->decoded.truncate( 0 ); } bool Unstructured::isEmpty() const { return d_func()->decoded.isEmpty(); } //------------------------------ //------------------------------ Structured::Structured( Content *p ) : Base( new StructuredPrivate, p ) { } Structured::Structured( Content *p, const QByteArray &s ) : Base( new StructuredPrivate, p ) { from7BitString( s ); } Structured::Structured( Content *p, const QString &s, const QByteArray &cs ) : Base( new StructuredPrivate, p ) { fromUnicodeString( s, cs ); } kmime_mk_dptr_ctor( Structured, Base ) Structured::~Structured() { } void Structured::from7BitString( const QByteArray &s ) { Q_D(Structured); if ( d->encCS.isEmpty() ) { d->encCS = defaultCharset(); } const char *cursor = s.constData(); parse( cursor, cursor + s.length() ); } QString Structured::asUnicodeString() const { return QString::fromLatin1( as7BitString( false ) ); } void Structured::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(Structured); d->encCS = cachedCharset( b ); from7BitString( s.toLatin1() ); } //------------------------------ //-----
      ------------------------- Address::Address( Content *p ) : Structured( new AddressPrivate, p ) { } Address::Address( Content *p, const QByteArray &s ) : Structured( new AddressPrivate, p ) { from7BitString( s ); } Address::Address( Content *p, const QString &s, const QByteArray &cs ) : Structured( new AddressPrivate, p ) { fromUnicodeString( s, cs ); } kmime_mk_dptr_ctor( Address, Structured ) Address:: ~Address() { } // helper method used in AddressList and MailboxList static bool stringToMailbox( const QByteArray &address, const QString &displayName, Types::Mailbox &mbox ) { Types::AddrSpec addrSpec; mbox.setName( displayName ); const char *cursor = address.constData(); if ( !parseAngleAddr( cursor, cursor + address.length(), addrSpec ) ) { if ( !parseAddrSpec( cursor, cursor + address.length(), addrSpec ) ) { kWarning() << "Invalid address"; return false; } } mbox.setAddress( addrSpec ); return true; } //-----
      ------------------------- //------------------------------ kmime_mk_trivial_ctor_with_dptr( MailboxList, Address ) kmime_mk_dptr_ctor( MailboxList, Address ) QByteArray MailboxList::as7BitString( bool withHeaderType ) const { const Q_D(MailboxList); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } foreach ( Types::Mailbox mbox, d->mailboxList ) { rv += mbox.as7BitString( d->encCS ); rv += ", "; } rv.resize( rv.length() - 2 ); return rv; } void MailboxList::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(MailboxList); d->encCS = cachedCharset( b ); from7BitString( encodeRFC2047String( s, b, false ) ); } QString MailboxList::asUnicodeString() const { return prettyAddresses().join( QLatin1String( ", " ) ); } void MailboxList::clear() { Q_D(MailboxList); d->mailboxList.clear(); } bool MailboxList::isEmpty() const { return d_func()->mailboxList.isEmpty(); } void MailboxList::addAddress( const Types::Mailbox &mbox ) { Q_D(MailboxList); d->mailboxList.append( mbox ); } void MailboxList::addAddress( const QByteArray &address, const QString &displayName ) { Q_D(MailboxList); Types::Mailbox mbox; if ( stringToMailbox( address, displayName, mbox ) ) { d->mailboxList.append( mbox ); } } QList< QByteArray > MailboxList::addresses() const { QList rv; foreach ( Types::Mailbox mbox, d_func()->mailboxList ) { rv.append( mbox.address() ); } return rv; } QStringList MailboxList::displayNames() const { QStringList rv; foreach ( Types::Mailbox mbox, d_func()->mailboxList ) { rv.append( mbox.name() ); } return rv; } QStringList MailboxList::prettyAddresses() const { QStringList rv; foreach ( Types::Mailbox mbox, d_func()->mailboxList ) { rv.append( mbox.prettyAddress() ); } return rv; } Types::Mailbox::List MailboxList::mailboxes() const { return d_func()->mailboxList; } bool MailboxList::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(MailboxList); // examples: // from := "From:" mailbox-list CRLF // sender := "Sender:" mailbox CRLF // parse an address-list: QList maybeAddressList; if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) { return false; } d->mailboxList.clear(); // extract the mailboxes and complain if there are groups: QList::Iterator it; for ( it = maybeAddressList.begin(); it != maybeAddressList.end() ; ++it ) { if ( !(*it).displayName.isEmpty() ) { KMIME_WARN << "mailbox groups in header disallowing them! Name: \"" << (*it).displayName << "\"" << endl; } d->mailboxList += (*it).mailboxList; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( SingleMailbox, MailboxList ) //@endcond bool SingleMailbox::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(MailboxList); if ( !MailboxList::parse( scursor, send, isCRLF ) ) { return false; } if ( d->mailboxList.count() > 1 ) { KMIME_WARN << "multiple mailboxes in header allowing only a single one!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( AddressList, Address ) kmime_mk_dptr_ctor( AddressList, Address ) //@endcond QByteArray AddressList::as7BitString( bool withHeaderType ) const { const Q_D(AddressList); if ( d->addressList.isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } foreach ( Types::Address addr, d->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv += mbox.as7BitString( d->encCS ); rv += ", "; } } rv.resize( rv.length() - 2 ); return rv; } void AddressList::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(AddressList); d->encCS = cachedCharset( b ); from7BitString( encodeRFC2047String( s, b, false ) ); } QString AddressList::asUnicodeString() const { return prettyAddresses().join( QLatin1String( ", " ) ); } void AddressList::clear() { Q_D(AddressList); d->addressList.clear(); } bool AddressList::isEmpty() const { return d_func()->addressList.isEmpty(); } void AddressList::addAddress( const Types::Mailbox &mbox ) { Q_D(AddressList); Types::Address addr; addr.mailboxList.append( mbox ); d->addressList.append( addr ); } void AddressList::addAddress( const QByteArray &address, const QString &displayName ) { Q_D(AddressList); Types::Address addr; Types::Mailbox mbox; if ( stringToMailbox( address, displayName, mbox ) ) { addr.mailboxList.append( mbox ); d->addressList.append( addr ); } } QList< QByteArray > AddressList::addresses() const { QList rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox.address() ); } } return rv; } QStringList AddressList::displayNames() const { QStringList rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox.name() ); } } return rv; } QStringList AddressList::prettyAddresses() const { QStringList rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox.prettyAddress() ); } } return rv; } Types::Mailbox::List AddressList::mailboxes() const { Types::Mailbox::List rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox ); } } return rv; } bool AddressList::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(AddressList); QList maybeAddressList; if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) { return false; } d->addressList = maybeAddressList; return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( Token, Structured ) kmime_mk_dptr_ctor( Token, Structured ) //@endcond QByteArray Token::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } if ( withHeaderType ) { return typeIntro() + d_func()->token; } return d_func()->token; } void Token::clear() { Q_D(Token); d->token.clear(); } bool Token::isEmpty() const { return d_func()->token.isEmpty(); } QByteArray Token::token() const { return d_func()->token; } void Token::setToken( const QByteArray &t ) { Q_D(Token); d->token = t; } bool Token::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Token); clear(); eatCFWS( scursor, send, isCRLF ); // must not be empty: if ( scursor == send ) { return false; } QPair maybeToken; if ( !parseToken( scursor, send, maybeToken, false /* no 8bit chars */ ) ) { return false; } d->token = QByteArray( maybeToken.first, maybeToken.second ); // complain if trailing garbage is found: eatCFWS( scursor, send, isCRLF ); if ( scursor != send ) { KMIME_WARN << "trailing garbage after token in header allowing " "only a single token!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( PhraseList, Structured ) //@endcond QByteArray PhraseList::as7BitString( bool withHeaderType ) const { const Q_D(PhraseList); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } for ( int i = 0; i < d->phraseList.count(); ++i ) { // FIXME: only encode when needed, quote when needed, etc. rv += encodeRFC2047String( d->phraseList[i], d->encCS, false, false ); if ( i != d->phraseList.count() - 1 ) { rv += ", "; } } return rv; } QString PhraseList::asUnicodeString() const { return d_func()->phraseList.join( QLatin1String( ", " ) ); } void PhraseList::clear() { Q_D(PhraseList); d->phraseList.clear(); } bool PhraseList::isEmpty() const { return d_func()->phraseList.isEmpty(); } QStringList PhraseList::phrases() const { return d_func()->phraseList; } bool PhraseList::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(PhraseList); d->phraseList.clear(); while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // empty entry ending the list: OK. if ( scursor == send ) { return true; } // empty entry: ignore. if ( *scursor == ',' ) { scursor++; continue; } QString maybePhrase; if ( !parsePhrase( scursor, send, maybePhrase, isCRLF ) ) { return false; } d->phraseList.append( maybePhrase ); eatCFWS( scursor, send, isCRLF ); // non-empty entry ending the list: OK. if ( scursor == send ) { return true; } // comma separating the phrases: eat. if ( *scursor == ',' ) { scursor++; } } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( DotAtom, Structured ) //@endcond QByteArray DotAtom::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += d_func()->dotAtom.toLatin1(); // FIXME: encoding? return rv; } QString DotAtom::asUnicodeString() const { return d_func()->dotAtom; } void DotAtom::clear() { Q_D(DotAtom); d->dotAtom.clear(); } bool DotAtom::isEmpty() const { return d_func()->dotAtom.isEmpty(); } bool DotAtom::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(DotAtom); QString maybeDotAtom; if ( !parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) { return false; } d->dotAtom = maybeDotAtom; eatCFWS( scursor, send, isCRLF ); if ( scursor != send ) { KMIME_WARN << "trailing garbage after dot-atom in header allowing " "only a single dot-atom!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( Parametrized, Structured ) kmime_mk_dptr_ctor( Parametrized, Structured ) //@endcond QByteArray Parametrized::as7BitString( bool withHeaderType ) const { const Q_D(Parametrized); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } bool first = true; for ( QMap::ConstIterator it = d->parameterHash.constBegin(); it != d->parameterHash.constEnd(); ++it ) { if ( !first ) { rv += "; "; } else { first = false; } rv += it.key().toLatin1() + '='; if ( isUsAscii( it.value() ) ) { QByteArray tmp = it.value().toLatin1(); addQuotes( tmp, true ); // force quoting, eg. for whitespaces in parameter value rv += tmp; } else { // FIXME: encoded strings are not allowed inside quotes, OTOH we need to quote whitespaces... rv += "\"" + encodeRFC2047String( it.value(), d->encCS ) + "\""; } } return rv; } QString Parametrized::parameter( const QString &key ) const { return d_func()->parameterHash.value( key ); } void Parametrized::setParameter( const QString &key, const QString &value ) { Q_D(Parametrized); d->parameterHash.insert( key, value ); } bool Parametrized::isEmpty() const { return d_func()->parameterHash.isEmpty(); } void Parametrized::clear() { Q_D(Parametrized); d->parameterHash.clear(); } bool Parametrized::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(Parametrized); d->parameterHash.clear(); if ( !parseParameterList( scursor, send, d->parameterHash, isCRLF ) ) { return false; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( Ident, Address ) kmime_mk_dptr_ctor( Ident, Address ) //@endcond QByteArray Ident::as7BitString( bool withHeaderType ) const { const Q_D(Ident); if ( d->msgIdList.isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } foreach ( Types::AddrSpec addr, d->msgIdList ) { rv += '<'; rv += addr.asString().toLatin1(); // FIXME: change parsing to use QByteArrays rv += "> "; } rv.resize( rv.length() - 1 ); return rv; } void Ident::clear() { Q_D(Ident); d->msgIdList.clear(); } bool Ident::isEmpty() const { return d_func()->msgIdList.isEmpty(); } bool Ident::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(Ident); // msg-id := "<" id-left "@" id-right ">" // id-left := dot-atom-text / no-fold-quote / local-part // id-right := dot-atom-text / no-fold-literal / domain // // equivalent to: // msg-id := angle-addr d->msgIdList.clear(); while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // empty entry ending the list: OK. if ( scursor == send ) { return true; } // empty entry: ignore. if ( *scursor == ',' ) { scursor++; continue; } AddrSpec maybeMsgId; if ( !parseAngleAddr( scursor, send, maybeMsgId, isCRLF ) ) { return false; } d->msgIdList.append( maybeMsgId ); eatCFWS( scursor, send, isCRLF ); // header end ending the list: OK. if ( scursor == send ) { return true; } // regular item separator: eat it. if ( *scursor == ',' ) { scursor++; } } return true; } QList Ident::identifiers() const { QList rv; foreach ( Types::AddrSpec addr, d_func()->msgIdList ) { rv.append( addr.asString().toLatin1() ); // FIXME change parsing to create QByteArrays } return rv; } void Ident::appendIdentifier( const QByteArray &id ) { Q_D(Ident); QByteArray tmp = id; if ( !tmp.startsWith( '<' ) ) { tmp.prepend( '<' ); } if ( !tmp.endsWith( '>' ) ) { tmp.append( '>' ); } AddrSpec msgId; const char *cursor = tmp.constData(); if ( parseAngleAddr( cursor, cursor + tmp.length(), msgId ) ) { d->msgIdList.append( msgId ); } else { kWarning() << "Unable to parse address spec!"; } } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( SingleIdent, Ident ) //@endcond QByteArray SingleIdent::identifier() const { if ( d_func()->msgIdList.isEmpty() ) { return QByteArray(); } return identifiers().first(); } void SingleIdent::setIdentifier( const QByteArray &id ) { Q_D(SingleIdent); d->msgIdList.clear(); appendIdentifier( id ); } bool SingleIdent::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(SingleIdent); if ( !Ident::parse( scursor, send, isCRLF ) ) { return false; } if ( d->msgIdList.count() > 1 ) { KMIME_WARN << "more than one msg-id in header " << "allowing only a single one!" << endl; } return true; } //------------------------------ } // namespace Generics //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ReturnPath, Generics::Address, Return-Path ) //@endcond QByteArray ReturnPath::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += '<' + d_func()->mailbox.as7BitString( d_func()->encCS ) + '>'; return rv; } void ReturnPath::clear() { Q_D(ReturnPath); d->mailbox.setAddress( Types::AddrSpec() ); d->mailbox.setName( QString() ); } bool ReturnPath::isEmpty() const { const Q_D(ReturnPath); return !d->mailbox.hasAddress() && !d->mailbox.hasName(); } bool ReturnPath::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(ReturnPath); eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } const char * oldscursor = scursor; Mailbox maybeMailbox; if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { // mailbox parsing failed, but check for empty brackets: scursor = oldscursor; if ( *scursor != '<' ) { return false; } scursor++; eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '>' ) { return false; } scursor++; // prepare a Null mailbox: AddrSpec emptyAddrSpec; maybeMailbox.setName( QString() ); maybeMailbox.setAddress( emptyAddrSpec ); } else { // check that there was no display-name: if ( maybeMailbox.hasName() ) { KMIME_WARN << "display-name \"" << maybeMailbox.name() << "\" in Return-Path!" << endl; } } d->mailbox = maybeMailbox; // see if that was all: eatCFWS( scursor, send, isCRLF ); // and warn if it wasn't: if ( scursor != send ) { KMIME_WARN << "trailing garbage after angle-addr in Return-Path!" << endl; } return true; } //------------------------------ //------------------------------------ // NOTE: Do *not* register Generic with HeaderFactory, since its type() is changeable. Generic::Generic() : Generics::Unstructured( new GenericPrivate ) { } Generic::Generic( const char *t ) : Generics::Unstructured( new GenericPrivate ) { setType( t ); } Generic::Generic( const char *t, Content *p ) : Generics::Unstructured( new GenericPrivate, p ) { setType( t ); } Generic::Generic( const char *t, Content *p, const QByteArray &s ) : Generics::Unstructured( new GenericPrivate, p ) { from7BitString( s ); setType( t ); } Generic::Generic( const char *t, Content *p, const QString &s, const QByteArray &cs ) : Generics::Unstructured( new GenericPrivate, p ) { fromUnicodeString( s, cs ); setType( t ); } Generic::~Generic() { } void Generic::clear() { Q_D(Generic); delete[] d->type; d->type = 0; Unstructured::clear(); } bool Generic::isEmpty() const { return d_func()->type == 0 || Unstructured::isEmpty(); } const char *Generic::type() const { return d_func()->type; } void Generic::setType( const char *type ) { Q_D(Generic); if ( d->type ) { delete[] d->type; } if ( type ) { d->type = new char[strlen( type )+1]; strcpy( d->type, type ); } else { d->type = 0; } } //------------------------------------ //---------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name( MessageID, Generics::SingleIdent, Message-Id ) //@endcond void MessageID::generate( const QByteArray &fqdn ) { setIdentifier( uniqueString() + '@' + fqdn + '>' ); } //--------------------------------- //------------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Control, Generics::Structured, Control ) //@endcond QByteArray Control::as7BitString( bool withHeaderType ) const { const Q_D(Control); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += d->name; if ( !d->parameter.isEmpty() ) { rv += ' ' + d->parameter; } return rv; } void Control::clear() { Q_D(Control); d->name.clear(); d->parameter.clear(); } bool Control::isEmpty() const { return d_func()->name.isEmpty(); } QByteArray Control::controlType() const { return d_func()->name; } QByteArray Control::parameter() const { return d_func()->parameter; } bool Control::isCancel() const { return d_func()->name.toLower() == "cancel"; } void Control::setCancel( const QByteArray &msgid ) { Q_D(Control); d->name = "cancel"; d->parameter = msgid; } bool Control::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Control); clear(); eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } const char *start = scursor; while ( scursor != send && !isspace( *scursor ) ) { ++scursor; } d->name = QByteArray( start, scursor - start ); eatCFWS( scursor, send, isCRLF ); d->parameter = QByteArray( scursor, send - scursor ); return true; } //----------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( MailCopiesTo, Generics::AddressList, Mail-Copies-To ) //@endcond QByteArray MailCopiesTo::as7BitString( bool withHeaderType ) const { QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } if ( !AddressList::isEmpty() ) { rv += AddressList::as7BitString( false ); } else { if ( d_func()->alwaysCopy ) { rv += "poster"; } else if ( d_func()->neverCopy ) { rv += "nobody"; } } return rv; } QString MailCopiesTo::asUnicodeString() const { if ( !AddressList::isEmpty() ) { return AddressList::asUnicodeString(); } if ( d_func()->alwaysCopy ) { return QLatin1String( "poster" ); } if ( d_func()->neverCopy ) { return QLatin1String( "nobody" ); } return QString(); } void MailCopiesTo::clear() { Q_D(MailCopiesTo); AddressList::clear(); d->alwaysCopy = false; d->neverCopy = false; } bool MailCopiesTo::isEmpty() const { return AddressList::isEmpty() && !(d_func()->alwaysCopy || d_func()->neverCopy); } bool MailCopiesTo::alwaysCopy() const { return !AddressList::isEmpty() || d_func()->alwaysCopy; } void MailCopiesTo::setAlwaysCopy() { Q_D(MailCopiesTo); clear(); d->alwaysCopy = true; } bool MailCopiesTo::neverCopy() const { return d_func()->neverCopy; } void MailCopiesTo::setNeverCopy() { Q_D(MailCopiesTo); clear(); d->neverCopy = true; } bool MailCopiesTo::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(MailCopiesTo); clear(); if ( send - scursor == 5 ) { if ( qstrnicmp( "never", scursor, 5 ) == 0 ) { d->neverCopy = true; return true; } } if ( send - scursor == 6 ) { if ( qstrnicmp( "always", scursor, 6 ) == 0 || qstrnicmp( "poster", scursor, 6 ) == 0 ) { d->alwaysCopy = true; return true; } if ( qstrnicmp( "nobody", scursor, 6 ) == 0 ) { d->alwaysCopy = true; return true; } } return AddressList::parse( scursor, send, isCRLF ); } //------------------------------ //--------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Date, Generics::Structured, Date ) //@endcond QByteArray Date::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += d_func()->dateTime.toString( KDateTime::RFCDateDay ).toLatin1(); return rv; } void Date::clear() { Q_D(Date); d->dateTime = KDateTime(); } bool Date::isEmpty() const { return d_func()->dateTime.isNull() || !d_func()->dateTime.isValid(); } KDateTime Date::dateTime() const { return d_func()->dateTime; } void Date::setDateTime( const KDateTime &dt ) { Q_D(Date); d->dateTime = dt; } int Date::ageInDays() const { QDate today = QDate::currentDate(); return dateTime().date().daysTo(today); } bool Date::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Date); return parseDateTime( scursor, send, d->dateTime, isCRLF ); } //-------------------------------------- //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Newsgroups, Generics::Structured, Newsgroups ) kmime_mk_trivial_ctor_with_name( FollowUpTo, Newsgroups, Followup-To ) //@endcond QByteArray Newsgroups::as7BitString( bool withHeaderType ) const { const Q_D(Newsgroups); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } for ( int i = 0; i < d->groups.count(); ++i ) { rv += d->groups[ i ]; if ( i != d->groups.count() - 1 ) { rv += ','; } } return rv; } void Newsgroups::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_UNUSED( b ); Q_D(Newsgroups); from7BitString( s.toUtf8() ); d->encCS = cachedCharset( "UTF-8" ); } QString Newsgroups::asUnicodeString() const { return QString::fromUtf8( as7BitString( false ) ); } void Newsgroups::clear() { Q_D(Newsgroups); d->groups.clear(); } bool Newsgroups::isEmpty() const { return d_func()->groups.isEmpty(); } QList Newsgroups::groups() const { return d_func()->groups; } void Newsgroups::setGroups( const QList &groups ) { Q_D(Newsgroups); d->groups = groups; } bool Newsgroups::isCrossposted() const { return d_func()->groups.count() >= 2; } bool Newsgroups::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Newsgroups); clear(); forever { eatCFWS( scursor, send, isCRLF ); if ( scursor != send && *scursor == ',' ) { ++scursor; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return true; } const char *start = scursor; while ( scursor != send && !isspace( *scursor ) && *scursor != ',' ) { ++scursor; } QByteArray group( start, scursor - start ); d->groups.append( group ); } return true; } //-------------------------------- //-------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Lines, Generics::Structured, Lines ) //@endcond QByteArray Lines::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray num; num.setNum( d_func()->lines ); if ( withHeaderType ) { return typeIntro() + num; } return num; } QString Lines::asUnicodeString() const { if ( isEmpty() ) { return QString(); } return QString::number( d_func()->lines ); } void Lines::clear() { Q_D(Lines); d->lines = -1; } bool Lines::isEmpty() const { return d_func()->lines == -1; } int Lines::numberOfLines() const { return d_func()->lines; } void Lines::setNumberOfLines( int lines ) { Q_D(Lines); d->lines = lines; } bool Lines::parse( const char* &scursor, const char* const send, bool isCRLF ) { Q_D(Lines); eatCFWS( scursor, send, isCRLF ); if ( parseDigits( scursor, send, d->lines ) == 0 ) { clear(); return false; } return true; } //------------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ContentType, Generics::Parametrized, Content-Type ) //@endcond bool ContentType::isEmpty() const { return d_func()->mimeType.isEmpty(); } void ContentType::clear() { Q_D(ContentType); d->category = CCsingle; d->mimeType.clear(); d->mimeSubType.clear(); Parametrized::clear(); } QByteArray ContentType::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += mimeType(); if ( !Parametrized::isEmpty() ) { rv += "; " + Parametrized::as7BitString( false ); } return rv; } QByteArray ContentType::mimeType() const { return d_func()->mimeType + '/' + d_func()->mimeSubType; } QByteArray ContentType::mediaType() const { return d_func()->mimeType; } QByteArray ContentType::subType() const { return d_func()->mimeSubType; } void ContentType::setMimeType( const QByteArray &mimeType ) { Q_D(ContentType); int pos = mimeType.indexOf( '/' ); if ( pos < 0 ) { d->mimeType = mimeType; d->mimeSubType.clear(); } else { d->mimeType = mimeType.left( pos ); d->mimeSubType = mimeType.mid( pos + 1 ); } Parametrized::clear(); if ( isMultipart() ) { d->category = CCcontainer; } else { d->category = CCsingle; } } bool ContentType::isMediatype( const char *mediatype ) const { return strncasecmp( mediaType().constData(), mediatype, strlen( mediatype ) ) == 0; } bool ContentType::isSubtype( const char *subtype ) const { return strncasecmp( subType().constData(), subtype, strlen( subtype ) ) == 0; } bool ContentType::isText() const { return ( strncasecmp( mediaType().constData(), "text", 4 ) == 0 || isEmpty() ); } bool ContentType::isPlainText() const { return ( strcasecmp( mimeType().constData(), "text/plain" ) == 0 || isEmpty() ); } bool ContentType::isHTMLText() const { return strcasecmp( mimeType().constData(), "text/html" ) == 0; } bool ContentType::isImage() const { return strncasecmp( mediaType().constData(), "image", 5 ) == 0; } bool ContentType::isMultipart() const { return strncasecmp( mediaType().constData(), "multipart", 9 ) == 0; } bool ContentType::isPartial() const { return strcasecmp( mimeType().constData(), "message/partial" ) == 0; } QByteArray ContentType::charset() const { QByteArray ret = parameter( "charset" ).toLatin1(); if ( ret.isEmpty() || forceDefaultCharset() ) { //return the default-charset if necessary ret = defaultCharset(); } return ret; } void ContentType::setCharset( const QByteArray &s ) { setParameter( "charset", QString::fromLatin1( s ) ); } QByteArray ContentType::boundary() const { return parameter( "boundary" ).toLatin1(); } void ContentType::setBoundary( const QByteArray &s ) { setParameter( "boundary", QString::fromLatin1( s ) ); } QString ContentType::name() const { return parameter( "name" ); } void ContentType::setName( const QString &s, const QByteArray &cs ) { Q_D(ContentType); d->encCS = cs; setParameter( "name", s ); } QByteArray ContentType::id() const { return parameter( "id" ).toLatin1(); } void ContentType::setId( const QByteArray &s ) { setParameter( "id", s ); } int ContentType::partialNumber() const { QByteArray p = parameter( "number" ).toLatin1(); if ( !p.isEmpty() ) { return p.toInt(); } else { return -1; } } int ContentType::partialCount() const { QByteArray p = parameter( "total" ).toLatin1(); if ( !p.isEmpty() ) { return p.toInt(); } else { return -1; } } contentCategory ContentType::category() const { return d_func()->category; } void ContentType::setCategory( contentCategory c ) { Q_D(ContentType); d->category = c; } void ContentType::setPartialParams( int total, int number ) { setParameter( "number", QString::number( number ) ); setParameter( "total", QString::number( total ) ); } bool ContentType::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(ContentType); // content-type: type "/" subtype *(";" parameter) clear(); eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; // empty header } // type QPair maybeMimeType; if ( !parseToken( scursor, send, maybeMimeType, false /* no 8Bit */ ) ) { return false; } d->mimeType = QByteArray( maybeMimeType.first, maybeMimeType.second ).toLower(); // subtype eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '/' ) { return false; } scursor++; eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } QPair maybeSubType; if ( !parseToken( scursor, send, maybeSubType, false /* no 8bit */ ) ) { return false; } d->mimeSubType = QByteArray( maybeSubType.first, maybeSubType.second ).toLower(); // parameter list eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { goto success; // no parameters } if ( *scursor != ';' ) { return false; } scursor++; if ( !Parametrized::parse( scursor, send, isCRLF ) ) { return false; } // adjust category success: if ( isMultipart() ) { d->category = CCcontainer; } else { d->category = CCsingle; } return true; } //------------------------------ //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ContentTransferEncoding, Generics::Token, Content-Transfer-Encoding ) //@endcond typedef struct { const char *s; int e; } encTableType; static const encTableType encTable[] = { { "7Bit", CE7Bit }, { "8Bit", CE8Bit }, { "quoted-printable", CEquPr }, { "base64", CEbase64 }, { "x-uuencode", CEuuenc }, { "binary", CEbinary }, { 0, 0} }; void ContentTransferEncoding::clear() { Q_D(ContentTransferEncoding); d->decoded = true; d->cte = CE7Bit; Token::clear(); } contentEncoding ContentTransferEncoding::encoding() const { return d_func()->cte; } void ContentTransferEncoding::setEncoding( contentEncoding e ) { Q_D(ContentTransferEncoding); d->cte = e; for ( int i = 0; encTable[i].s != 0; ++i ) { if ( d->cte == encTable[i].e ) { setToken( encTable[i].s ); break; } } } bool ContentTransferEncoding::decoded() const { return d_func()->decoded; } void ContentTransferEncoding::setDecoded( bool decoded ) { Q_D(ContentTransferEncoding); d->decoded = decoded; } bool ContentTransferEncoding::needToEncode() const { const Q_D(ContentTransferEncoding); return d->decoded && (d->cte == CEquPr || d->cte == CEbase64); } bool ContentTransferEncoding::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(ContentTransferEncoding); clear(); if ( !Token::parse( scursor, send, isCRLF ) ) { return false; } // TODO: error handling in case of an unknown encoding? for ( int i = 0; encTable[i].s != 0; ++i ) { if ( strcasecmp( token().constData(), encTable[i].s ) == 0 ) { d->cte = ( contentEncoding )encTable[i].e; break; } } d->decoded = ( d->cte == CE7Bit || d->cte == CE8Bit ); return true; } //-------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ContentDisposition, Generics::Parametrized, Content-Disposition ) //@endcond QByteArray ContentDisposition::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } if ( d_func()->disposition == CDattachment ) { rv += "attachment"; } else if ( d_func()->disposition == CDinline ) { rv += "inline"; } else { return QByteArray(); } if ( !Parametrized::isEmpty() ) { rv += "; " + Parametrized::as7BitString( false ); } return rv; } bool ContentDisposition::isEmpty() const { return d_func()->disposition == CDInvalid; } void ContentDisposition::clear() { Q_D(ContentDisposition); d->disposition = CDInvalid; Parametrized::clear(); } contentDisposition ContentDisposition::disposition() const { return d_func()->disposition; } void ContentDisposition::setDisposition( contentDisposition disp ) { Q_D(ContentDisposition); d->disposition = disp; } QString KMime::Headers::ContentDisposition::filename() const { return parameter( "filename" ); } void ContentDisposition::setFilename( const QString &filename ) { setParameter( "filename", filename ); } bool ContentDisposition::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(ContentDisposition); clear(); // token QByteArray token; eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } QPair maybeToken; if ( !parseToken( scursor, send, maybeToken, false /* no 8Bit */ ) ) { return false; } token = QByteArray( maybeToken.first, maybeToken.second ).toLower(); if ( token == "inline" ) { d->disposition = CDinline; } else if ( token == "attachment" ) { d->disposition = CDattachment; } else { return false; } // parameter list eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return true; // no parameters } if ( *scursor != ';' ) { return false; } scursor++; return Parametrized::parse( scursor, send, isCRLF ); } //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Subject, Generics::Unstructured, Subject ) //@endcond bool Subject::isReply() const { return asUnicodeString().indexOf( QLatin1String( "Re:" ), 0, Qt::CaseInsensitive ) == 0; } QString Subject::stripOffPrefixes() const { QString str = asUnicodeString(); QStringList prefixRegExps; prefixRegExps << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:" << "Fwd:" << "FW:"; // construct a big regexp that // 1. is anchored to the beginning of str (sans whitespace) // 2. matches at least one of the part regexps in prefixRegExps QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*") .arg( prefixRegExps.join(")|(?:") ); QRegExp rx( bigRegExp, Qt::CaseInsensitive ); if ( !rx.isValid() ) { kWarning() << "bigRegExp = \"" << bigRegExp << "\"\n" << "prefix regexp is invalid!"; } else { // valid rx QString tmp = str; if ( rx.indexIn( tmp ) == 0 ) { return tmp.replace( 0, rx.matchedLength(), QString() ); } } return str; } //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentDescription, Generics::Unstructured, Content-Description ) kmime_mk_trivial_ctor_with_name( ContentLocation, Generics::Unstructured, Content-Location ) kmime_mk_trivial_ctor_with_name( From, Generics::MailboxList, From ) kmime_mk_trivial_ctor_with_name( Sender, Generics::SingleMailbox, Sender ) kmime_mk_trivial_ctor_with_name( To, Generics::AddressList, To ) kmime_mk_trivial_ctor_with_name( Cc, Generics::AddressList, Cc ) kmime_mk_trivial_ctor_with_name( Bcc, Generics::AddressList, Bcc ) kmime_mk_trivial_ctor_with_name( ReplyTo, Generics::AddressList, Reply-To ) kmime_mk_trivial_ctor_with_name( Keywords, Generics::PhraseList, Keywords ) kmime_mk_trivial_ctor_with_name( MIMEVersion, Generics::DotAtom, MIME-Version ) kmime_mk_trivial_ctor_with_name( ContentID, Generics::SingleIdent, Content-ID ) kmime_mk_trivial_ctor_with_name( Supersedes, Generics::SingleIdent, Supersedes ) kmime_mk_trivial_ctor_with_name( InReplyTo, Generics::Ident, In-Reply-To ) kmime_mk_trivial_ctor_with_name( References, Generics::Ident, References ) kmime_mk_trivial_ctor_with_name( Organization, Generics::Unstructured, Organization ) kmime_mk_trivial_ctor_with_name( UserAgent, Generics::Unstructured, User-Agent ) //@endcond } // namespace Headers } // namespace KMime diff --git a/kmime/kmime_headers.h b/kmime/kmime_headers.h index a95aad382..02d79b921 100644 --- a/kmime/kmime_headers.h +++ b/kmime/kmime_headers.h @@ -1,1480 +1,1480 @@ /* -*- c++ -*- kmime_headers.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the various header classes: - header's base class defining the common interface - generic base classes for different types of fields - incompatible, Structured-based field classes - compatible, Unstructured-based field classes @brief Defines the various headers classes. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #ifndef __KMIME_HEADERS_H__ #define __KMIME_HEADERS_H__ #include "kmime_export.h" #include "kmime_header_parsing.h" #include #include #include #include #include #include #include #include namespace KMime { class Content; namespace Headers { class BasePrivate; enum contentCategory { CCsingle, CCcontainer, CCmixedPart, CCalternativePart }; /** Various possible values for the "Content-Transfer-Encoding" header. */ enum contentEncoding { CE7Bit, ///< 7bit CE8Bit, ///< 8bit CEquPr, ///< quoted-printable CEbase64, ///< base64 CEuuenc, ///< uuencode CEbinary ///< binary }; /** Various possible values for the "Content-Disposition" header. */ enum contentDisposition { CDInvalid, ///< Default, invalid value CDinline, ///< inline CDattachment, ///< attachment CDparallel ///< parallel (invalid, do not use) }; //often used charset // TODO: get rid of this! static const QByteArray Latin1( "ISO-8859-1" ); //@cond PRIVATE // internal macro to generate default constructors #define kmime_mk_trivial_ctor( subclass ) \ public: \ explicit subclass( Content *parent = 0 ); \ subclass( Content *parent, const QByteArray &s ); \ subclass( Content *parent, const QString &s, const QByteArray &charset ); \ ~subclass(); #define kmime_mk_dptr_ctor( subclass ) \ protected: \ explicit subclass( subclass##Private *d, KMime::Content *parent = 0 ); #define kmime_mk_trivial_ctor_with_name( subclass ) \ kmime_mk_trivial_ctor( subclass ) \ const char *type() const; //@endcond // // // HEADER'S BASE CLASS. DEFINES THE COMMON INTERFACE // // /** Baseclass of all header-classes. It represents a header-field as described in RFC-822. */ class KMIME_EXPORT Base { public: /** A list of headers. */ typedef QList List; /** Creates an empty header with a parent-content. */ explicit Base( KMime::Content *parent = 0 ); /** Destructor. */ virtual ~Base(); /** Returns the parent of this header. */ KMime::Content *parent() const; /** Sets the parent for this header to @p parent. */ void setParent( KMime::Content *parent ); /** Parses the given string. Take care of RFC2047-encoded strings. @param s The encoded header data. */ virtual void from7BitString( const QByteArray &s ) = 0; /** Returns the encoded header. @param withHeaderType Specifies whether the header-type should be included. */ virtual QByteArray as7BitString( bool withHeaderType = true ) const = 0; /** Returns the charset that is used for RFC2047-encoding. */ QByteArray rfc2047Charset() const; /** Sets the charset for RFC2047-encoding. @param cs The new charset used for RFC2047 encoding. */ void setRFC2047Charset( const QByteArray &cs ); /** Returns the default charset. */ QByteArray defaultCharset() const; /** Returns if the default charset is mandatory. */ bool forceDefaultCharset() const; /** Parses the given string and set the charset. @param s The header data as unicode string. @param b The charset preferred for encoding. */ virtual void fromUnicodeString( const QString &s, const QByteArray &b ) = 0; /** Returns the decoded content of the header without the header-type. */ virtual QString asUnicodeString() const = 0; /** Deletes. */ virtual void clear() = 0; /** Checks if this header contains any data. */ virtual bool isEmpty() const = 0; /** Returns the type of this header (e.g. "From"). */ virtual const char *type() const; /** Checks if this header is of type @p t. */ bool is( const char *t ) const; /** Checks if this header is a MIME header. */ bool isMimeHeader() const; /** Checks if this header is a X-Header. */ bool isXHeader() const; protected: /** Helper method, returns the header prefix including ":". */ QByteArray typeIntro() const; //@cond PRIVATE BasePrivate *d_ptr; kmime_mk_dptr_ctor( Base ) //@endcond private: Q_DECLARE_PRIVATE(Base) Q_DISABLE_COPY(Base) }; // // // GENERIC BASE CLASSES FOR DIFFERENT TYPES OF FIELDS // // namespace Generics { class UnstructuredPrivate; /** Abstract base class for unstructured header fields (e.g. "Subject", "Comment", "Content-description"). Features: Decodes the header according to RFC2047, incl. RFC2231 extensions to encoded-words. Subclasses need only re-implement @p const @p char* @p type(). */ // known issues: // - uses old decodeRFC2047String function, instead of our own... class KMIME_EXPORT Unstructured : public Base { //@cond PRIVATE kmime_mk_dptr_ctor( Unstructured ) //@endcond public: explicit Unstructured( Content *p = 0 ); Unstructured( Content *p, const QByteArray &s ); Unstructured( Content *p, const QString &s, const QByteArray &cs ); ~Unstructured(); virtual void from7BitString( const QByteArray &s ); virtual QByteArray as7BitString( bool withHeaderType=true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; private: Q_DECLARE_PRIVATE(Unstructured) }; class StructuredPrivate; /** @brief Base class for structured header fields. This is the base class for all structured header fields. It contains parsing methods for all basic token types found in rfc2822. @section Parsing At the basic level, there are tokens & tspecials (rfc2045), atoms & specials, quoted-strings, domain-literals (all rfc822) and encoded-words (rfc2047). As a special token, we have the comment. It is one of the basic tokens defined in rfc822, but it's parsing relies in part on the basic token parsers (e.g. comments may contain encoded-words). Also, most upper-level parsers (notably those for phrase and dot-atom) choose to ignore any comment when parsing. Then there are the real composite tokens, which are made up of one or more of the basic tokens (and semantically invisible comments): phrases (rfc822 with rfc2047) and dot-atoms (rfc2822). This finishes the list of supported token types. Subclasses will provide support for more higher-level tokens, where necessary, using these parsers. @author Marc Mutz */ class KMIME_EXPORT Structured : public Base { public: explicit Structured( Content *p = 0 ); Structured( Content *p, const QByteArray &s ); Structured( Content *p, const QString &s, const QByteArray &cs ); ~Structured(); virtual void from7BitString( const QByteArray &s ); virtual QString asUnicodeString() const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); protected: /** This method parses the raw header and needs to be implemented in every sub-class. @param scursor Pointer to the start of the data still to parse. @param send Pointer to the end of the data. @param isCRLF true if input string is terminated with a CRLF. */ virtual bool parse( const char* &scursor, const char *const send, bool isCRLF = false ) = 0; //@cond PRIVATE kmime_mk_dptr_ctor( Structured ) //@endcond private: Q_DECLARE_PRIVATE(Structured) }; class AddressPrivate; /** Base class for all address related headers. */ class KMIME_EXPORT Address : public Structured { public: explicit Address( Content *p = 0 ); Address( Content *p, const QByteArray &s ); Address( Content *p, const QString &s, const QByteArray &cs ); ~Address(); protected: //@cond PRIVATE kmime_mk_dptr_ctor( Address ) //@endcond private: Q_DECLARE_PRIVATE(Address) }; class MailboxListPrivate; /** Base class for headers that deal with (possibly multiple) addresses, but don't allow groups. @see RFC 2822, section 3.4 */ class KMIME_EXPORT MailboxList : public Address { //@cond PRIVATE kmime_mk_trivial_ctor( MailboxList ) kmime_mk_dptr_ctor( MailboxList ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Adds an address to this header. @param mbox A Mailbox object specifying the address. */ void addAddress( const Types::Mailbox &mbox ); /** Adds an address to this header. @param address The actual email address, with or without angle brackets. @param displayName An optional name associated with the address. */ void addAddress( const QByteArray &address, const QString &displayName = QString() ); /** Returns a list of all addresses in this header, regardless of groups. */ QList addresses() const; /** Returns a list of all display names associated with the addresses in this header. An empty entry is added for addresses that do not have a display name. */ QStringList displayNames() const; /** Returns a list of assembled display name / address strings of the following form: "Display Name <address>". These are unicode strings without any transport encoding, ie. they are only suitable for displaying. */ QStringList prettyAddresses() const; /** Returns a list of mailboxes listed in this header. */ Types::Mailbox::List mailboxes() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(MailboxList) }; class SingleMailboxPrivate; /** Base class for headers that deal with exactly one mailbox (e.g. Sender). */ class KMIME_EXPORT SingleMailbox : public MailboxList { //@cond PRIVATE kmime_mk_trivial_ctor( SingleMailbox ) //@endcond protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(SingleMailbox) }; class AddressListPrivate; /** Base class for headers that deal with (possibly multiple) addresses, allowing groups. Note: Groups are parsed but not represented in the API yet. All addresses in groups are listed as if they would not be part of a group. @todo Add API for groups? @see RFC 2822, section 3.4 */ class KMIME_EXPORT AddressList : public Address { //@cond PRIVATE kmime_mk_trivial_ctor( AddressList ) kmime_mk_dptr_ctor( AddressList ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Adds an address to this header. @param mbox A Mailbox object specifying the address. */ void addAddress( const Types::Mailbox &mbox ); /** Adds an address to this header. @param address The actual email address, with or without angle brackets. @param displayName An optional name associated with the address. */ void addAddress( const QByteArray &address, const QString &displayName = QString() ); /** Returns a list of all addresses in this header, regardless of groups. */ QList addresses() const; /** Returns a list of all display names associated with the addresses in this header. An empty entry is added for addresses that don't have a display name. */ QStringList displayNames() const; /** Returns a list of assembled display name / address strings of the following form: "Display Name <address>". These are unicode strings without any transport encoding, ie. they are only suitable for displaying. */ QStringList prettyAddresses() const; /** Returns a list of mailboxes listed in this header. */ Types::Mailbox::List mailboxes() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(AddressList) }; class IdentPrivate; /** Base class for headers which deal with a list of msg-id's. @see RFC 2822, section 3.6.4 */ class KMIME_EXPORT Ident : public Address { //@cond PRIVATE kmime_mk_trivial_ctor( Ident ) kmime_mk_dptr_ctor( Ident ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the list of identifiers contained in this header. Note: - Identifiers are not enclosed in angle-brackets. - Identifiers are listed in the same order as in the header. */ QList identifiers() const; /** Appends a new identifier to this header. @param id The identifier to append, with or without angle-brackets. */ void appendIdentifier( const QByteArray &id ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(Ident) }; class SingleIdentPrivate; /** Base class for headers which deal with a single msg-id. @see RFC 2822, section 3.6.4 */ class KMIME_EXPORT SingleIdent : public Ident { //@cond PRIVATE kmime_mk_trivial_ctor( SingleIdent ) //@endcond public: /** Returns the identifier contained in this header. Note: The identifiers is not enclosed in angle-brackets. */ QByteArray identifier() const; /** Sets the identifier. @param id The new identifier with or without angle-brackets. */ void setIdentifier( const QByteArray &id ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(SingleIdent) }; class TokenPrivate; /** Base class for headers which deal with a single atom. */ class KMIME_EXPORT Token : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( Token ) kmime_mk_dptr_ctor( Token ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the token. */ QByteArray token() const; /** Sets the token to @p t, */ void setToken( const QByteArray &t ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(Token) }; class PhraseListPrivate; /** Base class for headers containing a list of phrases. */ class KMIME_EXPORT PhraseList : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( PhraseList ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns the list of phrases contained in this header. */ QStringList phrases() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(PhraseList) }; class DotAtomPrivate; /** Base class for headers containing a dot atom. */ class KMIME_EXPORT DotAtom : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( DotAtom ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(DotAtom) }; class ParametrizedPrivate; /** Base class for headers containing a parameter list such as "Content-Type". */ class KMIME_EXPORT Parametrized : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( Parametrized ) kmime_mk_dptr_ctor( Parametrized ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual bool isEmpty() const; virtual void clear(); /** Returns the value of the specified parameter. @param key The parameter name. */ QString parameter( const QString &key ) const; /** Sets the parameter @p key to @p value. @param key The parameter name. @param value The new value for @p key. */ void setParameter( const QString &key, const QString &value ); protected: virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(Parametrized) }; } // namespace Generics // // // INCOMPATIBLE, GSTRUCTURED-BASED FIELDS: // // class ReturnPathPrivate; /** Represents the Return-Path header field. @see RFC 2822, section 3.6.7 */ class KMIME_EXPORT ReturnPath : public Generics::Address { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ReturnPath ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(ReturnPath) }; // Address et al.: // rfc(2)822 headers: /** Represent a "From" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT From : public Generics::MailboxList { kmime_mk_trivial_ctor_with_name( From ) }; /** Represents a "Sender" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT Sender : public Generics::SingleMailbox { kmime_mk_trivial_ctor_with_name( Sender ) }; /** Represents a "To" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT To : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( To ) }; /** Represents a "Cc" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT Cc : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( Cc ) }; /** Represents a "Bcc" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT Bcc : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( Bcc ) }; /** Represents a "ReplyTo" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT ReplyTo : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( ReplyTo ) }; class MailCopiesToPrivate; /** Represents a "Mail-Copies-To" header. @see http://www.newsreaders.com/misc/mail-copies-to.html */ class KMIME_EXPORT MailCopiesTo : public Generics::AddressList { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( MailCopiesTo ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns true if a mail copy was explicitly requested. */ bool alwaysCopy() const; /** Sets the header to "poster". */ void setAlwaysCopy(); /** Returns true if a mail copy was explicitly denied. */ bool neverCopy() const; /** Sets the header to "never". */ void setNeverCopy(); protected: virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(MailCopiesTo) }; class ContentTransferEncodingPrivate; /** Represents a "Content-Transfer-Encoding" header. @see RFC 2045, section 6. */ class KMIME_EXPORT ContentTransferEncoding : public Generics::Token { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentTransferEncoding ) //@endcond public: virtual void clear(); /** Returns the encoding specified in this header. */ contentEncoding encoding() const; /** Sets the encoding to @p e. */ void setEncoding( contentEncoding e ); /** Returns whether the Content containing this header is already decoded. */ // KDE5: rename to isDecoded(). bool decoded() const; /** Set whether the Content containing this header is already decoded. For instance, if you fill your Content with already-encoded base64 data, you will want to setDecoded( false ). */ void setDecoded( bool decoded = true ); /** Returns whether the Content containing this header needs to be encoded (i.e., if decoded() is true and encoding() is base64 or quoted-printable). */ bool needToEncode() const; protected: virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(ContentTransferEncoding) }; /** Represents a "Keywords" header. @see RFC 2822, section 3.6.5. */ class KMIME_EXPORT Keywords : public Generics::PhraseList { kmime_mk_trivial_ctor_with_name( Keywords ) }; // DotAtom: /** Represents a "MIME-Version" header. @see RFC 2045, section 4. */ class KMIME_EXPORT MIMEVersion : public Generics::DotAtom { kmime_mk_trivial_ctor_with_name( MIMEVersion ) }; // Ident: /** Represents a "Message-ID" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT MessageID : public Generics::SingleIdent { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( MessageID ) //@endcond public: /** Generate a message identifer. @param fqdn A fully qualified domain name. */ void generate( const QByteArray &fqdn ); }; /** Represents a "Content-ID" header. */ class KMIME_EXPORT ContentID : public Generics::SingleIdent { kmime_mk_trivial_ctor_with_name( ContentID ) }; /** Represents a "Supersedes" header. */ class KMIME_EXPORT Supersedes : public Generics::SingleIdent { kmime_mk_trivial_ctor_with_name( Supersedes ) }; /** Represents a "In-Reply-To" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT InReplyTo : public Generics::Ident { kmime_mk_trivial_ctor_with_name( InReplyTo ) }; /** Represents a "References" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT References : public Generics::Ident { kmime_mk_trivial_ctor_with_name( References ) }; class ContentTypePrivate; /** Represents a "Content-Type" header. @see RFC 2045, section 5. */ class KMIME_EXPORT ContentType : public Generics::Parametrized { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentType ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the mimetype. */ QByteArray mimeType() const; /** Returns the media type (first part of the mimetype). */ QByteArray mediaType() const; /** Returns the mime sub-type (second part of the mimetype). */ QByteArray subType() const; /** Sets the mimetype and clears already existing parameters. @param mimeType The new mimetype. */ void setMimeType( const QByteArray &mimeType ); /** Tests if the media type equals @p mediatype. */ bool isMediatype( const char *mediatype ) const; /** Tests if the mime sub-type equals @p subtype. */ bool isSubtype( const char *subtype ) const; /** Returns true if the associated MIME entity is a text. */ bool isText() const; /** Returns true if the associated MIME entity is a plain text. */ bool isPlainText() const; /** Returns true if the associated MIME entity is a HTML file. */ bool isHTMLText() const; /** Returns true if the associated MIME entity is an image. */ bool isImage() const; /** Returns true if the associated MIME entity is a mulitpart container. */ bool isMultipart() const; /** Returns true if the associated MIME entity contains partial data. @see partialNumber(), partialCount() */ bool isPartial() const; /** Returns the charset for the associated MIME entity. */ QByteArray charset() const; /** Sets the charset. */ void setCharset( const QByteArray &s ); /** Returns the boundary (for mulitpart containers). */ QByteArray boundary() const; /** Sets the mulitpart container boundary. */ void setBoundary( const QByteArray &s ); /** Returns the name of the associated MIME entity. */ QString name() const; /** Sets the name to @p s using charset @p cs. */ void setName( const QString &s, const QByteArray &cs ); /** Returns the identifier of the associated MIME entity. */ QByteArray id() const; /** Sets the identifier. */ void setId( const QByteArray &s ); /** Returns the position of this part in a multi-part set. @see isPartial(), partialCount() */ int partialNumber() const; /** Returns the total number of parts in a multi-part set. @see isPartial(), partialNumber() */ int partialCount() const; /** Sets parameters of a partial MIME entity. @param total The total number of entities in the multi-part set. @param number The number of this entity in a multi-part set. */ void setPartialParams( int total, int number ); // TODO: document contentCategory category() const; void setCategory( contentCategory c ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(ContentType) }; class ContentDispositionPrivate; /** Represents a "Content-Disposition" header. @see RFC 2183 */ class KMIME_EXPORT ContentDisposition : public Generics::Parametrized { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentDisposition ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual bool isEmpty() const; virtual void clear(); /** Returns the content disposition. */ contentDisposition disposition() const; /** Sets the content disposition. @param disp The new content disposition. */ void setDisposition( contentDisposition disp ); /** Returns the suggested filename for the associated MIME part. This is just a convenience function, it is equivalent to calling parameter( "filename" ); */ QString filename() const; /** Sets the suggested filename for the associated MIME part. This is just a convenience function, it is equivalent to calling setParameter( "filename", filename ); @param filename The filename. */ void setFilename( const QString &filename ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE( ContentDisposition ) }; // // // COMPATIBLE GUNSTRUCTURED-BASED FIELDS: // // class GenericPrivate; /** Represents an arbitrary header, that can contain any header-field. Adds a type over Unstructured. @see Unstructured */ class KMIME_EXPORT Generic : public Generics::Unstructured { public: Generic(); Generic( const char *t ); Generic( const char *t, Content *p ); Generic( const char *t, Content *p, const QByteArray &s ); Generic( const char *t, Content *p, const QString &s, const QByteArray &cs ); ~Generic(); virtual void clear(); virtual bool isEmpty() const; virtual const char *type() const; void setType( const char *type ); private: Q_DECLARE_PRIVATE( Generic ) }; /** Represents a "Subject" header. @see RFC 2822, section 3.6.5. */ class KMIME_EXPORT Subject : public Generics::Unstructured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Subject ) //@endcond public: bool isReply() const; QString stripOffPrefixes() const; }; /** Represents a "Organization" header. */ class KMIME_EXPORT Organization : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( Organization ) }; /** Represents a "Content-Description" header. */ class KMIME_EXPORT ContentDescription : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( ContentDescription ) }; /** Represents a "Content-Location" header. @since 4.2 */ class KMIME_EXPORT ContentLocation : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( ContentLocation ) }; class ControlPrivate; /** Represents a "Control" header. @see RFC 1036, section 3. */ class KMIME_EXPORT Control : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Control ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the control message type. */ QByteArray controlType() const; /** Returns the control message parameter. */ QByteArray parameter() const; /** Returns true if this is a cancel control message. @see RFC 1036, section 3.1. */ bool isCancel() const; /** Changes this header into a cancel control message for the given message-id. @param msgid The message-id of the article that should be canceled. */ void setCancel( const QByteArray &msgid ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE(Control) }; class DatePrivate; /** Represents a "Date" header. @see RFC 2822, section 3.3. */ class KMIME_EXPORT Date : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Date ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the date contained in this header. */ KDateTime dateTime() const; /** Sets the date. */ void setDateTime( const KDateTime &dt ); /** Returns the age of the message. */ int ageInDays() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE( Date ) }; class NewsgroupsPrivate; /** Represents a "Newsgroups" header. @see RFC 1036, section 2.1.3. */ class KMIME_EXPORT Newsgroups : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Newsgroups ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns the list of newsgroups. */ QList groups() const; /** Sets the newsgroup list. */ void setGroups( const QList &groups ); /** Returns true if this message has been cross-posted, i.e. if it has been posted to multiple groups. */ bool isCrossposted() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE( Newsgroups ) }; /** Represents a "Followup-To" header. @see RFC 1036, section 2.2.3. */ class KMIME_EXPORT FollowUpTo : public Newsgroups { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( FollowUpTo ) //@endcond }; class LinesPrivate; /** Represents a "Lines" header. @see RFC 1036, section 2.2.12. */ class KMIME_EXPORT Lines : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Lines ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns the number of lines, undefined if isEmpty() returns true. */ int numberOfLines() const; /** Sets the number of lines to @p lines. */ void setNumberOfLines( int lines ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE( Lines ) }; /** Represents a "User-Agent" header. */ class KMIME_EXPORT UserAgent : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( UserAgent ) }; } //namespace Headers } //namespace KMime // undefine code generation macros again #undef kmime_mk_trivial_ctor #undef kmime_mk_dptr_ctor #undef kmime_mk_trivial_ctor_with_name #endif // __KMIME_HEADERS_H__ diff --git a/kmime/kmime_mdn.cpp b/kmime/kmime_mdn.cpp index a062c51f4..bf0a1acac 100644 --- a/kmime/kmime_mdn.cpp +++ b/kmime/kmime_mdn.cpp @@ -1,283 +1,283 @@ /* -*- c++ -*- kmime_mdn.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2002 Marc Mutz 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 API for handling @ref MIME data and provides functions for supporting Message Disposition Notifications (MDNs), also known as email return receipts. @brief Provides support for Message Disposition Notifications. @authors Marc Mutz \ */ #include "kmime_mdn.h" #include "kmime_version.h" #include "kmime_util.h" #include #include #include #include #include // gethostname namespace KMime { namespace MDN { static const struct { DispositionType dispositionType; const char * string; const char * description; } dispositionTypes[] = { { Displayed, "displayed", I18N_NOOP("The message sent on ${date} to ${to} with subject " "\"${subject}\" has been displayed. This is no guarantee that " "the message has been read or understood.") }, { Deleted, "deleted", I18N_NOOP("The message sent on ${date} to ${to} with subject " "\"${subject}\" has been deleted unseen. This is no guarantee " "that the message will not be \"undeleted\" and nonetheless " "read later on.") }, { Dispatched, "dispatched", I18N_NOOP("The message sent on ${date} to ${to} with subject " "\"${subject}\" has been dispatched. This is no guarantee " "that the message will not be read later on.") }, { Processed, "processed", I18N_NOOP("The message sent on ${date} to ${to} with subject " "\"${subject}\" has been processed by some automatic means.") }, { Denied, "denied", I18N_NOOP("The message sent on ${date} to ${to} with subject " "\"${subject}\" has been acted upon. The sender does not wish " "to disclose more details to you than that.") }, { Failed, "failed", I18N_NOOP("Generation of a Message Disposition Notification for the " "message sent on ${date} to ${to} with subject \"${subject}\" " "failed. Reason is given in the Failure: header field below.") } }; static const int numDispositionTypes = sizeof dispositionTypes / sizeof *dispositionTypes; static const char *stringFor( DispositionType d ) { for ( int i = 0 ; i < numDispositionTypes ; ++i ) { if ( dispositionTypes[i].dispositionType == d ) { return dispositionTypes[i].string; } } return 0; } // // disposition-modifier // static const struct { DispositionModifier dispositionModifier; const char * string; } dispositionModifiers[] = { { Error, "error" }, { Warning, "warning" }, { Superseded, "superseded" }, { Expired, "expired" }, { MailboxTerminated, "mailbox-terminated" } }; static const int numDispositionModifiers = sizeof dispositionModifiers / sizeof * dispositionModifiers; static const char *stringFor( DispositionModifier m ) { for ( int i = 0 ; i < numDispositionModifiers ; ++i ) { if ( dispositionModifiers[i].dispositionModifier == m ) { return dispositionModifiers[i].string; } } return 0; } // // action-mode (part of disposition-mode) // static const struct { ActionMode actionMode; const char * string; } actionModes[] = { { ManualAction, "manual-action" }, { AutomaticAction, "automatic-action" } }; static const int numActionModes = sizeof actionModes / sizeof *actionModes; static const char *stringFor( ActionMode a ) { for ( int i = 0 ; i < numActionModes ; ++i ) { if ( actionModes[i].actionMode == a ) { return actionModes[i].string; } } return 0; } // // sending-mode (part of disposition-mode) // static const struct { SendingMode sendingMode; const char * string; } sendingModes[] = { { SentManually, "MDN-sent-manually" }, { SentAutomatically, "MDN-sent-automatically" } }; static const int numSendingModes = sizeof sendingModes / sizeof *sendingModes; static const char *stringFor( SendingMode s ) { for ( int i = 0 ; i < numSendingModes ; ++i ) { if ( sendingModes[i].sendingMode == s ) { return sendingModes[i].string; } } return 0; } static QByteArray dispositionField( DispositionType d, ActionMode a, SendingMode s, const QList & m ) { // mandatory parts: Disposition: foo/baz; bar QByteArray result = "Disposition: "; result += stringFor( a ); result += '/'; result += stringFor( s ); result += "; "; result += stringFor( d ); // optional parts: Disposition: foo/baz; bar/mod1,mod2,mod3 bool first = true; for ( QList::const_iterator mt = m.begin() ; mt != m.end() ; ++mt ) { if ( first ) { result += '/'; first = false; } else { result += ','; } result += stringFor( *mt ); } return result + '\n'; } static QByteArray finalRecipient( const QString &recipient ) { if ( recipient.isEmpty() ) { return QByteArray(); } else { return "Final-Recipient: rfc822; " + encodeRFC2047String( recipient, "utf-8" ) + '\n'; } } static QByteArray orginalRecipient( const QByteArray & recipient ) { if ( recipient.isEmpty() ) { return QByteArray(); } else { return "Original-Recipient: " + recipient + '\n'; } } static QByteArray originalMessageID( const QByteArray &msgid ) { if ( msgid.isEmpty() ) { return QByteArray(); } else { return "Original-Message-ID: " + msgid + '\n'; } } static QByteArray reportingUAField() { char hostName[256]; if ( gethostname( hostName, 255 ) ) { hostName[0] = '\0'; // gethostname failed: pretend empty string } else { hostName[255] = '\0'; // gethostname may have returned 255 chars (man page) } return QByteArray("Reporting-UA: ") + QByteArray( hostName ) + QByteArray( "; KMime " KMIME_VERSION_STRING "\n" ); } QByteArray dispositionNotificationBodyContent( const QString &r, const QByteArray &o, const QByteArray &omid, DispositionType d, ActionMode a, SendingMode s, const QList &m, const QString &special ) { // in Perl: chomp(special) QString spec; if ( special.endsWith('\n') ) { spec = special.left( special.length() - 1 ); } else { spec = special; } // std headers: QByteArray result = reportingUAField(); result += orginalRecipient( o ); result += finalRecipient( r ); result += originalMessageID( omid ); result += dispositionField( d, a, s, m ); // headers that are only present for certain disposition {types,modifiers}: if ( d == Failed ) { result += "Failure: " + encodeRFC2047String( spec, "utf-8" ) + '\n'; } else if ( m.contains( Error ) ) { result += "Error: " + encodeRFC2047String( spec, "utf-8" ) + '\n'; } else if ( m.contains( Warning ) ) { result += "Warning: " + encodeRFC2047String( spec, "utf-8" ) + '\n'; } return result; } QString descriptionFor( DispositionType d, const QList & ) { for ( int i = 0 ; i < numDispositionTypes ; ++i ) { if ( dispositionTypes[i].dispositionType == d ) { return i18n( dispositionTypes[i].description ); } } kWarning() << "KMime::MDN::descriptionFor(): No such disposition type:" << ( int )d; return QString(); } } // namespace MDN } // namespace KMime diff --git a/kmime/kmime_mdn.h b/kmime/kmime_mdn.h index 73abf2a6a..3f3026fa6 100644 --- a/kmime/kmime_mdn.h +++ b/kmime/kmime_mdn.h @@ -1,196 +1,196 @@ /* -*- c++ -*- kmime_mdn.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2002 Marc Mutz 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 API for handling @ref MIME data and provides functions for supporting Message Disposition Notifications (MDNs), also known as email return receipts. @brief Provides support for Message Disposition Notifications. @authors Marc Mutz \ @glossary @anchor MDN @b MDN: see @ref Message_Disposition_Notification @glossary @anchor Message_Disposition_Notification @b Message @b Disposition @b Notification: Return receipts for email are called message disposition notifications. Their format and usage is outlined in @ref RFC2298. @glossary @anchor RFC2298 @anchor rfc2298 @b RFC @b 2298: RFC that defines the An Extensible Message Format for Message Disposition Notifications. */ #ifndef __KMIME_MDN_H__ #define __KMIME_MDN_H__ #include "kmime_export.h" #include #include class QByteArray; namespace KMime { namespace MDN { /** The following disposition-types are defined: @li Displayed The message has been displayed by the UA to someone reading the recipient's mailbox. There is no guarantee that the content has been read or understood. @li Dispatched The message has been sent somewhere in some manner (e.g., printed, faxed, forwarded) without necessarily having been previously displayed to the user. The user may or may not see the message later. @li Processed The message has been processed in some manner (i.e., by some sort of rules or server) without being displayed to the user. The user may or may not see the message later, or there may not even be a human user associated with the mailbox. @li Deleted The message has been deleted. The recipient may or may not have seen the message. The recipient might "undelete" the message at a later time and read the message. @li Denied The recipient does not wish the sender to be informed of the message's disposition. A UA may also siliently ignore message disposition requests in this situation. @li Failed A failure occurred that prevented the proper generation of an MDN. More information about the cause of the failure may be contained in a Failure field. The "failed" disposition type is not to be used for the situation in which there is is some problem in processing the message other than interpreting the request for an MDN. The "processed" or other disposition type with appropriate disposition modifiers is to be used in such situations. IOW: @p Displayed when - well -displayed @p Dispatched when forwarding unseen ( == new ) @p Processed (maybe) when piping unseen, but probably never used @p Deleted when deleting unseen @p Denied on user command @p Failed on Disposition-Notification-Options containing unknown required options. ( == @em any required options ) @p Failed needs a description in the @p special parameter. */ enum DispositionType { Displayed, Read = Displayed, Deleted, Dispatched, Forwarded = Dispatched, Processed, Denied, Failed }; /** The following disposition modifiers are defined: @li Error An error of some sort occurred that prevented successful processing of the message. Further information is contained in an Error field. @li Warning The message was successfully processed but some sort of exceptional condition occurred. Further information is contained in a Warning field. @li Superseded The message has been automatically rendered obsolete by another message received. The recipient may still access and read the message later. @li Expired The message has reached its expiration date and has been automatically removed from the recipient's mailbox. @li MailboxTerminated The recipient's mailbox has been terminated and all message in it automatically removed. */ enum DispositionModifier { Error, Warning, Superseded, Expired, MailboxTerminated }; /** The following disposition modes are defined: @li ManualAction The disposition described by the disposition type was a result of an explicit instruction by the user rather than some sort of automatically performed action. @li AutomaticAction The disposition described by the disposition type was a result of an automatic action, rather than an explicit instruction by the user for this message. IOW: @p ManualAction for user-driven actions, @p AutomanticAction for filtering. */ enum ActionMode { ManualAction, AutomaticAction }; /** @li SentManually The user explicitly gave permission for this particular MDN to be sent. @li SentAutomatically The MDN was sent because the MUA had previously been configured to do so automatically. IOW: @p SentManually for when we have asked the user @p SentAutomatically when we use the default specified by the user */ enum SendingMode { SentManually, SentAutomatically }; /** Generates the content of the message/disposition-notification body part. */ KMIME_EXPORT extern QByteArray dispositionNotificationBodyContent( const QString &finalRecipient, const QByteArray &originalRecipient, const QByteArray &originalMsgID, DispositionType disposition, ActionMode actionMode, SendingMode sendingMode, const QList &dispositionModifers=QList(), const QString &special=QString() ); KMIME_EXPORT extern QString descriptionFor( DispositionType d, const QList &m=QList() ); } // namespace MDN } // namespace KMime #endif // __KMIME_MDN_H__ diff --git a/kmime/kmime_message.cpp b/kmime/kmime_message.cpp index 95da65172..fd9e6c323 100644 --- a/kmime/kmime_message.cpp +++ b/kmime/kmime_message.cpp @@ -1,161 +1,161 @@ /* kmime_message.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 "kmime_message.h" #include "kmime_message_p.h" #include "kmime_util_p.h" using namespace KMime; namespace KMime { Message::Message() : Content( new MessagePrivate( this ) ) { } Message::Message(MessagePrivate * d) : Content( d ) { } Message::~Message() { } void Message::parse() { // KDE5: remove this virtual reimplementation. Content::parse(); } QByteArray Message::assembleHeaders() { // Create the mandatory fields (RFC5322) if they do not exist already. date( true ); from( true ); // Make sure the mandatory MIME-Version field (RFC2045) is present and valid. Headers::MIMEVersion *mimeVersion = header( true ); mimeVersion->from7BitString( "1.0" ); // Assemble all header fields. return Content::assembleHeaders(); } void Message::clear() { // KDE5: remove this virtual reimplementation. Content::clear(); } Headers::Base *Message::getHeaderByType( const char *type ) { // KDE5: remove this virtual reimplementation. return headerByType( type ); } Headers::Base *Message::headerByType( const char *type ) { // KDE5: remove this virtual reimplementation. return Content::headerByType( type ); } void Message::setHeader( Headers::Base *h ) { // KDE5: remove this virtual reimplementation. Content::setHeader( h ); } bool Message::removeHeader( const char *type ) { // KDE5: remove this virtual reimplementation. return Content::removeHeader( type ); } bool Message::isTopLevel() const { return true; } Content *Message::mainBodyPart( const QByteArray &type ) { KMime::Content *c = this; while ( c ) { // not a multipart message if ( !c->contentType()->isMultipart() ) { if ( c->contentType()->mimeType() == type || type.isEmpty() ) { return c; } return 0; } // empty multipart if ( c->contents().count() == 0 ) { return 0; } // multipart/alternative if ( c->contentType()->subType() == "alternative" ) { if ( type.isEmpty() ) { return c->contents().first(); } foreach ( Content *c1, c->contents() ) { if ( c1->contentType()->mimeType() == type ) { return c1; } } return 0; } c = c->contents().first(); } return 0; } // @cond PRIVATE #define kmime_mk_header_accessor( type, method ) \ Headers::type *Message::method( bool create ) { \ return header( create ); \ } kmime_mk_header_accessor( MessageID, messageID ) kmime_mk_header_accessor( Subject, subject ) kmime_mk_header_accessor( Date, date ) kmime_mk_header_accessor( Organization, organization ) kmime_mk_header_accessor( From, from ) kmime_mk_header_accessor( ReplyTo, replyTo ) kmime_mk_header_accessor( To, to ) kmime_mk_header_accessor( Cc, cc ) kmime_mk_header_accessor( Bcc, bcc ) kmime_mk_header_accessor( References, references ) kmime_mk_header_accessor( UserAgent, userAgent ) kmime_mk_header_accessor( InReplyTo, inReplyTo ) kmime_mk_header_accessor( Sender, sender ) #undef kmime_mk_header_accessor // @endcond } diff --git a/kmime/kmime_message.h b/kmime/kmime_message.h index b2a725b2a..fd476b0ee 100644 --- a/kmime/kmime_message.h +++ b/kmime/kmime_message.h @@ -1,191 +1,191 @@ /* kmime_message.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 __KMIME_MESSAGE_H__ #define __KMIME_MESSAGE_H__ #include "kmime_export.h" #include "kmime_content.h" #include "kmime_headers.h" #include "boolflags.h" namespace boost { template class shared_ptr; } namespace KMime { class MessagePrivate; /** Represents a (email) message. */ class KMIME_EXPORT Message : public Content { public: /** A list of messages. */ typedef QList List; /** A shared pointer to a message object. */ typedef boost::shared_ptr Ptr; /** Creates an empty Message. */ Message(); /** Destroys this Message. */ ~Message(); /* reimpl */ virtual void parse(); /* reimpl */ virtual void clear(); /* reimpl */ virtual KDE_DEPRECATED KMime::Headers::Base *getHeaderByType( const char *type ); /* reimpl */ virtual KMime::Headers::Base *headerByType( const char *type ); /* reimpl */ virtual void setHeader( KMime::Headers::Base *h ); /* reimpl */ virtual bool removeHeader( const char *type ); // KDE5: Why are these virtual? /** Returns the Message-ID header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::MessageID *messageID( bool create = true ); /** Returns the Subject header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Subject *subject( bool create = true ); /** Returns the Date header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Date *date( bool create = true ); /** Returns the From header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::From *from( bool create = true ); /** Returns the Organization header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Organization *organization( bool create = true ); /** Returns the Reply-To header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::ReplyTo *replyTo( bool create = true ); /** Returns the To header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::To *to( bool create = true ); /** Returns the Cc header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Cc *cc( bool create = true ); /** Returns the Bcc header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Bcc *bcc( bool create = true ); /** Returns the References header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::References *references( bool create = true ); /** Returns the User-Agent header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::UserAgent *userAgent( bool create = true ); /** Returns the In-Reply-To header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::InReplyTo *inReplyTo( bool create = true ); /** Returns the Sender header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Sender *sender( bool create = true ); /* reimpl */ virtual bool isTopLevel() const; /** Returns the first main body part of a given type, taking multipart/mixed and multipart/alternative nodes into consideration. Eg. \c bodyPart("text/html") will return a html content object if that is provided in a multipart/alternative node, but not if it's the non-first child node of a multipart/mixed node (ie. an attachment). @param type The mimetype of the body part, if not given, the first body part will be returned, regardless of it's type. */ Content* mainBodyPart( const QByteArray &type = QByteArray() ); protected: /* reimpl */ virtual QByteArray assembleHeaders(); // @cond PRIVATE explicit Message( MessagePrivate *d ); // @endcond private: Q_DECLARE_PRIVATE( Message ) }; // class Message } // namespace KMime #endif // __KMIME_MESSAGE_H__ diff --git a/kmime/kmime_newsarticle.cpp b/kmime/kmime_newsarticle.cpp index 39740bf69..b2e72ecb3 100644 --- a/kmime/kmime_newsarticle.cpp +++ b/kmime/kmime_newsarticle.cpp @@ -1,112 +1,112 @@ /* kmime_newsarticle.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 "kmime_newsarticle.h" #include "kmime_message_p.h" #include "kmime_util_p.h" using namespace KMime; namespace KMime { class NewsArticlePrivate : public MessagePrivate { public: NewsArticlePrivate( NewsArticle *q ) : MessagePrivate( q ) { } Q_DECLARE_PUBLIC(NewsArticle) }; NewsArticle::NewsArticle() : Message( new NewsArticlePrivate( this ) ) { } NewsArticle::~NewsArticle() { } void NewsArticle::parse() { // KDE5: remove this virtual reimplementation. Message::parse(); } QByteArray NewsArticle::assembleHeaders() { // Create the mandatory Lines: field. lines( true ); // Assemble all header fields. return Message::assembleHeaders(); } void NewsArticle::clear() { // KDE5: remove this virtual reimplementation. Message::clear(); } Headers::Base * NewsArticle::getHeaderByType( const char *type ) { // KDE5: remove this virtual reimplementation. return headerByType( type ); } Headers::Base * NewsArticle::headerByType( const char *type ) { // KDE5: remove this virtual reimplementation. return Message::headerByType( type ); } void NewsArticle::setHeader( Headers::Base *h ) { // KDE5: remove this virtual reimplementation. Message::setHeader( h ); } bool NewsArticle::removeHeader( const char *type ) { // KDE5: remove this virtual reimplementation. return Message::removeHeader( type ); } // @cond PRIVATE #define kmime_mk_header_accessor( type, method ) \ Headers::type* NewsArticle::method( bool create ) { \ return header( create ); \ } kmime_mk_header_accessor( Control, control ) kmime_mk_header_accessor( Lines, lines ) kmime_mk_header_accessor( Supersedes, supersedes ) kmime_mk_header_accessor( MailCopiesTo, mailCopiesTo ) kmime_mk_header_accessor( Newsgroups, newsgroups ) kmime_mk_header_accessor( FollowUpTo, followUpTo ) #undef kmime_mk_header_accessor // @endcond } // namespace KMime diff --git a/kmime/kmime_newsarticle.h b/kmime/kmime_newsarticle.h index 14343dde0..d95144043 100644 --- a/kmime/kmime_newsarticle.h +++ b/kmime/kmime_newsarticle.h @@ -1,125 +1,125 @@ /* kmime_newsarticle.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 __KMIME_NEWSARTICLE_H__ #define __KMIME_NEWSARTICLE_H__ #include "kmime_export.h" #include "kmime_message.h" #include namespace KMime { class NewsArticlePrivate; class KMIME_EXPORT NewsArticle : public Message { public: /** A shared pointer to a news article. */ typedef boost::shared_ptr Ptr; /** Creates a NewsArticle object. */ NewsArticle(); /** Destroys this NewsArticle. */ ~NewsArticle(); /* reimpl */ virtual void parse(); /* reimpl */ virtual void clear(); /* reimpl */ virtual KDE_DEPRECATED KMime::Headers::Base * getHeaderByType( const char *type ); /* reimpl */ virtual KMime::Headers::Base * headerByType( const char *type ); /* reimpl */ virtual void setHeader( KMime::Headers::Base *h ); /* reimpl */ virtual bool removeHeader( const char *type ); /** Returns the Control header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Control *control( bool create = true ); /** Returns the Supersedes header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Supersedes *supersedes( bool create = true ); /** Returns the Mail-Copies-To header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::MailCopiesTo *mailCopiesTo( bool create = true ); /** Returns the Newsgroups header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Newsgroups *newsgroups( bool create = true ); /** Returns the Follow-Up-To header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::FollowUpTo *followUpTo( bool create = true ); /** Returns the Lines header. @param create If true, create the header if it doesn't exist yet. */ virtual KMime::Headers::Lines *lines( bool create = true ); protected: /* reimpl */ virtual QByteArray assembleHeaders(); private: Q_DECLARE_PRIVATE( NewsArticle ) }; // class NewsArticle } // namespace KMime //@cond PRIVATE // super class trait specialization namespace KPIMUtils { template <> struct SuperClass : public SuperClassTrait{}; } //@endcond #endif // __KMIME_NEWSARTICLE_H__ diff --git a/kmime/kmime_parsers.cpp b/kmime/kmime_parsers.cpp index 3220f060f..81c2f4ece 100644 --- a/kmime/kmime_parsers.cpp +++ b/kmime/kmime_parsers.cpp @@ -1,506 +1,506 @@ /* kmime_parsers.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 "kmime_parsers.h" #include #include using namespace KMime::Parser; namespace KMime { namespace Parser { MultiPart::MultiPart( const QByteArray &src, const QByteArray &boundary ) { s_rc=src; b_oundary=boundary; } bool MultiPart::parse() { QByteArray b = "--" + b_oundary, part; int pos1=0, pos2=0, blen=b.length(); p_arts.clear(); //find the first valid boundary while ( 1 ) { if ( ( pos1 = s_rc.indexOf( b, pos1 ) ) == -1 || pos1 == 0 || s_rc[pos1-1] == '\n' ) { //valid boundary found or no boundary at all break; } pos1 += blen; //boundary found but not valid => skip it; } if ( pos1 > -1 ) { pos1 += blen; if ( s_rc[pos1] == '-' && s_rc[pos1+1] == '-' ) { // the only valid boundary is the end-boundary // this message is *really* broken pos1 = -1; //we give up } else if ( ( pos1 - blen ) > 1 ) { //preamble present p_reamble = s_rc.left( pos1 - blen ); } } while ( pos1 > -1 && pos2 > -1 ) { //skip the rest of the line for the first boundary - the message-part starts here if ( ( pos1 = s_rc.indexOf( '\n', pos1 ) ) > -1 ) { //now search the next linebreak //now find the next valid boundary pos2=++pos1; //pos1 and pos2 point now to the beginning of the next line after the boundary while ( 1 ) { if ( ( pos2 = s_rc.indexOf( b, pos2 ) ) == -1 || s_rc[pos2-1] == '\n' ) { //valid boundary or no more boundaries found break; } pos2 += blen; //boundary is invalid => skip it; } if ( pos2 == -1 ) { // no more boundaries found part = s_rc.mid( pos1, s_rc.length() - pos1 ); //take the rest of the string p_arts.append( part ); pos1 = -1; pos2 = -1; //break; } else { part = s_rc.mid( pos1, pos2 - pos1 - 1 ); // pos2 - 1 (\n) is part of the boundary (see RFC 2046, section 5.1.1) p_arts.append( part ); - pos2 += blen; //pos2 points now to the first charakter after the boundary + pos2 += blen; //pos2 points now to the first character after the boundary if ( s_rc[pos2] == '-' && s_rc[pos2+1] == '-' ) { //end-boundary pos1 = pos2 + 2; //pos1 points now to the character directly after the end-boundary if ( ( pos1 = s_rc.indexOf( '\n', pos1 ) ) > -1 ) { //skip the rest of this line //everything after the end-boundary is considered as the epilouge e_pilouge = s_rc.mid( pos1 + 1, s_rc.length() - pos1 - 1 ); } pos1 = -1; pos2 = -1; //break } else { pos1 = pos2; //the search continues ... } } } } return !p_arts.isEmpty(); } //============================================================================= NonMimeParser::NonMimeParser( const QByteArray &src ) : s_rc( src ), p_artNr( -1 ), t_otalNr( -1 ) { } /** * try to guess the mimetype from the file-extension */ QByteArray NonMimeParser::guessMimeType( const QByteArray &fileName ) { QByteArray tmp, mimeType; int pos; if ( !fileName.isEmpty() ) { pos = fileName.lastIndexOf( '.' ); if ( pos++ != -1 ) { tmp = fileName.mid( pos, fileName.length() - pos).toUpper(); if ( tmp == "JPG" || tmp=="JPEG" ) { mimeType = "image/jpeg"; } else if ( tmp == "GIF") { mimeType = "image/gif"; } else if ( tmp == "PNG") { mimeType = "image/png"; } else if ( tmp == "TIFF" || tmp == "TIF") { mimeType = "image/tiff"; } else if ( tmp == "XPM") { mimeType = "image/x-xpixmap"; } else if ( tmp == "XBM") { mimeType = "image/x-xbitmap"; } else if ( tmp == "BMP") { mimeType = "image/bmp"; } else if ( tmp == "TXT" || tmp == "ASC" || tmp == "H" || tmp == "C" || tmp == "CC" || tmp == "CPP") { mimeType = "text/plain"; } else if ( tmp == "HTML" || tmp == "HTM" ) { mimeType = "text/html"; } else { mimeType = "application/octet-stream"; } } else { mimeType = "application/octet-stream"; } } else { mimeType = "application/octet-stream"; } return mimeType; } //============================================================================== UUEncoded::UUEncoded( const QByteArray &src, const QByteArray &subject ) : NonMimeParser( src ), s_ubject( subject ) {} bool UUEncoded::parse() { int currentPos=0; bool success=true, firstIteration=true; while ( success ) { int beginPos=currentPos, uuStart=currentPos, endPos=0, lineCount=0, MCount=0, pos=0, len=0; bool containsBegin=false, containsEnd=false; QByteArray tmp, fileName; if ( ( beginPos = QString( s_rc ). indexOf( QRegExp( "begin [0-9][0-9][0-9]" ), currentPos ) ) > -1 && ( beginPos == 0 || s_rc.at( beginPos - 1 ) == '\n') ) { containsBegin = true; uuStart = s_rc.indexOf( '\n', beginPos ); if ( uuStart == -1 ) {//no more line breaks found, we give up success = false; break; } else { uuStart++; //points now at the beginning of the next line } } else { beginPos=currentPos; } if ( ( endPos = s_rc. indexOf( "\nend", ( uuStart > 0 ) ? uuStart-1:0 ) ) == -1 ) { endPos = s_rc.length(); //no end found } else { containsEnd = true; } if ( ( containsBegin && containsEnd ) || firstIteration ) { //printf("beginPos=%d , uuStart=%d , endPos=%d\n", beginPos, uuStart, endPos); //all lines in a uuencoded text start with 'M' for ( int idx=uuStart; idx 10 || ( ( !containsBegin || !containsEnd ) && ( MCount < 15 ) ) ) { // harder check for splitted-articles success = false; break; //too many "non-M-Lines" found, we give up } if ( ( !containsBegin || !containsEnd ) && !s_ubject.isNull() ) { // message may be split up => parse subject QRegExp rx("[0-9]+/[0-9]+"); pos = rx.indexIn( QString( s_ubject ), 0 ); len = rx.matchedLength(); if ( pos != -1 ) { tmp = s_ubject.mid( pos, len ); pos = tmp.indexOf( '/' ); p_artNr = tmp.left( pos ).toInt(); t_otalNr = tmp.right( tmp.length() - pos - 1).toInt(); } else { success = false; break; //no "part-numbers" found in the subject, we give up } } //everything before "begin" is text if ( beginPos > 0 ) { t_ext.append( s_rc.mid( currentPos, beginPos - currentPos ) ); } if ( containsBegin ) { //everything between "begin ### " and the next LF is considered as the filename fileName = s_rc.mid( beginPos + 10, uuStart - beginPos - 11 ); } else { fileName = ""; } f_ilenames.append( fileName ); //everything beetween "begin" and "end" is uuencoded b_ins.append( s_rc.mid( uuStart, endPos - uuStart + 1 ) ); m_imeTypes.append( guessMimeType( fileName ) ); firstIteration = false; int next = s_rc.indexOf( '\n', endPos + 1 ); if ( next == -1 ) { //no more line breaks found, we give up success = false; break; } else { next++; //points now at the beginning of the next line } currentPos = next; } else { success = false; } } // append trailing text part of the article t_ext.append( s_rc.right( s_rc.length() - currentPos ) ); return ( ( b_ins.count() > 0 ) || isPartial() ); } //============================================================================== YENCEncoded::YENCEncoded( const QByteArray &src ) : NonMimeParser( src ) { } bool YENCEncoded::yencMeta( QByteArray &src, const QByteArray &name, int *value ) { bool found = false; QByteArray sought=name + '='; int iPos = src.indexOf( sought ); if ( iPos > -1 ) { int pos1 = src.indexOf( ' ', iPos ); int pos2 = src.indexOf( '\r', iPos ); int pos3 = src.indexOf( '\t', iPos ); int pos4 = src.indexOf( '\n', iPos ); if ( pos2 >= 0 && ( pos1 < 0 || pos1 > pos2 ) ) { pos1 = pos2; } if ( pos3 >= 0 && ( pos1 < 0 || pos1 > pos3 ) ) { pos1 = pos3; } if ( pos4 >= 0 && ( pos1 < 0 || pos1 > pos4 ) ) { pos1 = pos4; } iPos=src.lastIndexOf( '=', pos1 ) + 1; if ( iPos < pos1 ) { char c = src.at( iPos ); if ( c>='0' && c<='9' ) { found = true; *value = src.mid( iPos, pos1 - iPos ).toInt(); } } } return found; } bool YENCEncoded::parse() { int currentPos=0; bool success=true; while ( success ) { int beginPos=currentPos, yencStart=currentPos; bool containsPart=false; QByteArray fileName, mimeType; if ( ( beginPos = s_rc. indexOf( "=ybegin ", currentPos ) ) > -1 && ( beginPos == 0 || s_rc.at( beginPos - 1 ) == '\n' ) ) { yencStart = s_rc.indexOf( '\n', beginPos ); if ( yencStart == -1 ) { // no more line breaks found, give up success = false; break; } else { yencStart++; if ( s_rc.indexOf( "=ypart", yencStart ) == yencStart ) { containsPart = true; yencStart = s_rc.indexOf( '\n', yencStart ); if ( yencStart == -1 ) { success = false; break; } yencStart++; } } // Try to identify yenc meta data // Filenames can contain any embedded chars until end of line QByteArray meta = s_rc.mid( beginPos, yencStart - beginPos ); int namePos = meta.indexOf( "name=" ); if ( namePos == -1 ) { success = false; break; } int eolPos = meta.indexOf( '\r', namePos ); if ( eolPos == -1 ) { eolPos = meta.indexOf( '\n', namePos ); } if ( eolPos == -1 ) { success = false; break; } fileName = meta.mid( namePos + 5, eolPos - ( namePos + 5 ) ); // Other metadata is integer int yencLine; if ( !yencMeta( meta, "line", ¥cLine ) ) { success = false; break; } int yencSize; if ( !yencMeta( meta, "size", ¥cSize ) ) { success = false; break; } int partBegin, partEnd; if ( containsPart ) { if ( !yencMeta( meta, "part", &p_artNr ) ) { success = false; break; } if ( !yencMeta( meta, "begin", &partBegin ) || !yencMeta( meta, "end", &partEnd ) ) { success = false; break; } if ( !yencMeta( meta, "total", &t_otalNr ) ) { t_otalNr = p_artNr + 1; } if ( yencSize == partEnd - partBegin + 1 ) { t_otalNr = 1; } else { yencSize = partEnd - partBegin + 1; } } // We have a valid yenc header; now we extract the binary data int totalSize = 0; int pos = yencStart; int len = s_rc.length(); bool lineStart = true; int lineLength = 0; bool containsEnd = false; QByteArray binary; binary.resize( yencSize ); while ( pos < len ) { int ch = s_rc.at( pos ); if ( ch < 0 ) { ch += 256; } if ( ch == '\r' ) { if ( lineLength != yencLine && totalSize != yencSize ) { break; } pos++; } else if ( ch == '\n' ) { lineStart = true; lineLength = 0; pos++; } else { if ( ch == '=' ) { if ( pos + 1 < len ) { ch = s_rc.at( pos + 1 ); if ( lineStart && ch == 'y' ) { containsEnd = true; break; } pos += 2; ch -= 64+42; if ( ch < 0 ) { ch += 256; } if ( totalSize >= yencSize ) { break; } binary[totalSize++] = ch; lineLength++; } else { break; } } else { ch -= 42; if ( ch < 0 ) { ch += 256; } if ( totalSize >= yencSize ) { break; } binary[totalSize++] = ch; lineLength++; pos++; } lineStart = false; } } if ( !containsEnd ) { success = false; break; } if ( totalSize != yencSize ) { success = false; break; } // pos now points to =yend; get end data eolPos = s_rc.indexOf( '\n', pos ); if ( eolPos == -1 ) { success = false; break; } meta = s_rc.mid( pos, eolPos - pos ); if ( !yencMeta( meta, "size", &totalSize ) ) { success = false; break; } if ( totalSize != yencSize ) { success = false; break; } f_ilenames.append( fileName ); m_imeTypes.append( guessMimeType( fileName ) ); b_ins.append( binary ); //everything before "begin" is text if ( beginPos > 0 ) { t_ext.append( s_rc.mid( currentPos, beginPos - currentPos ) ); } currentPos = eolPos + 1; } else { success = false; } } // append trailing text part of the article t_ext.append( s_rc.right( s_rc.length() - currentPos ) ); return b_ins.count()>0; } } // namespace Parser } // namespace KMime diff --git a/kmime/kmime_parsers.h b/kmime/kmime_parsers.h index d4680b090..4112410f0 100644 --- a/kmime/kmime_parsers.h +++ b/kmime/kmime_parsers.h @@ -1,130 +1,130 @@ /* kmime_parsers.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 __KMIME_PARSERS__ #define __KMIME_PARSERS__ #include #include namespace KMime { namespace Parser { /** Helper-class: splits a multipart-message into single parts as described in RFC 2046 @internal */ class MultiPart { public: MultiPart( const QByteArray &src, const QByteArray &boundary ); ~MultiPart() {} bool parse(); QList parts() { return p_arts; } QByteArray preamble() { return p_reamble; } QByteArray epilouge() { return e_pilouge; } protected: QByteArray s_rc, b_oundary, p_reamble, e_pilouge; QList p_arts; }; /** Helper-class: abstract base class of all parsers for non-mime binary data (uuencoded, yenc) @internal */ class NonMimeParser { public: NonMimeParser( const QByteArray &src ); virtual ~NonMimeParser() {} virtual bool parse() = 0; bool isPartial() { return ( p_artNr > -1 && t_otalNr > -1 && t_otalNr != 1 ); } int partialNumber() { return p_artNr; } int partialCount() { return t_otalNr; } bool hasTextPart() { return ( t_ext.length() > 1 ); } QByteArray textPart() { return t_ext; } QList binaryParts() { return b_ins; } QList filenames() { return f_ilenames; } QList mimeTypes() { return m_imeTypes; } protected: static QByteArray guessMimeType( const QByteArray &fileName ); QByteArray s_rc, t_ext; QList b_ins, f_ilenames, m_imeTypes; int p_artNr, t_otalNr; }; /** Helper-class: tries to extract the data from a possibly uuencoded message @internal */ class UUEncoded : public NonMimeParser { public: UUEncoded( const QByteArray &src, const QByteArray &subject ); virtual bool parse(); protected: QByteArray s_ubject; }; /** Helper-class: tries to extract the data from a possibly yenc encoded message @internal */ class YENCEncoded : public NonMimeParser { public: YENCEncoded( const QByteArray &src ); virtual bool parse(); QList binaryParts() { return b_ins; } protected: QList b_ins; static bool yencMeta( QByteArray &src, const QByteArray &name, int *value ); }; } // namespace Parser } // namespace KMime #endif // __KMIME_PARSERS__ diff --git a/kmime/kmime_util.cpp b/kmime/kmime_util.cpp index b6b7daa57..9b1cab5e1 100644 --- a/kmime/kmime_util.cpp +++ b/kmime/kmime_util.cpp @@ -1,561 +1,561 @@ /* kmime_util.cpp - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 "kmime_util.h" #include "kmime_util_p.h" #include "kmime_header_parsing.h" #include "kmime_charfreq.h" #include #include // for strcasestr #include #include #include #include #include #include #include #include #include #include #include using namespace KMime; namespace KMime { QList c_harsetCache; QList l_anguageCache; QByteArray cachedCharset( const QByteArray &name ) { foreach ( const QByteArray& charset, c_harsetCache ) { if ( qstricmp( name.data(), charset.data() ) == 0 ) { return charset; } } c_harsetCache.append( name.toUpper() ); //kDebug(5320) << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count(); return c_harsetCache.last(); } QByteArray cachedLanguage( const QByteArray &name ) { foreach ( const QByteArray& language, l_anguageCache ) { if ( qstricmp( name.data(), language.data() ) == 0 ) { return language; } } l_anguageCache.append( name.toUpper() ); //kDebug(5320) << "KNMimeBase::cachedCharset() number of cs" << c_harsetCache.count(); return l_anguageCache.last(); } bool isUsAscii( const QString &s ) { uint sLength = s.length(); for ( uint i=0; i encodingsForData( const QByteArray &data ) { QList allowed; CharFreq cf( data ); switch ( cf.type() ) { case CharFreq::SevenBitText: allowed << Headers::CE7Bit; case CharFreq::EightBitText: allowed << Headers::CE8Bit; case CharFreq::SevenBitData: if ( cf.printableRatio() > 5.0/6.0 ) { // let n the length of data and p the number of printable chars. // Then base64 \approx 4n/3; qp \approx p + 3(n-p) // => qp < base64 iff p > 5n/6. allowed << Headers::CEquPr; allowed << Headers::CEbase64; } else { allowed << Headers::CEbase64; allowed << Headers::CEquPr; } break; case CharFreq::EightBitData: allowed << Headers::CEbase64; break; case CharFreq::None: default: Q_ASSERT( false ); } return allowed; } // "(),.:;<>@[\] const uchar specialsMap[16] = { 0x00, 0x00, 0x00, 0x00, // CTLs 0x20, 0xCA, 0x00, 0x3A, // SPACE ... '?' 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' 0x00, 0x00, 0x00, 0x00 // '`' ... DEL }; // "(),:;<>@[\]/=? const uchar tSpecialsMap[16] = { 0x00, 0x00, 0x00, 0x00, // CTLs 0x20, 0xC9, 0x00, 0x3F, // SPACE ... '?' 0x80, 0x00, 0x00, 0x1C, // '@' ... '_' 0x00, 0x00, 0x00, 0x00 // '`' ... DEL }; // all except specials, CTLs, SPACE. const uchar aTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x5F, 0x35, 0xFF, 0xC5, 0x7F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFE }; // all except tspecials, CTLs, SPACE. const uchar tTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x5F, 0x36, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFE }; // none except a-zA-Z0-9!*+-/ const uchar eTextMap[16] = { 0x00, 0x00, 0x00, 0x00, 0x40, 0x35, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xE0 }; QString decodeRFC2047String( const QByteArray &src, QByteArray &usedCS, const QByteArray &defaultCS, bool forceCS ) { QByteArray result; QByteArray spaceBuffer; const char *scursor = src.constData(); const char *send = scursor + src.length(); bool onlySpacesSinceLastWord = false; while ( scursor != send ) { // space if ( isspace( *scursor ) && onlySpacesSinceLastWord ) { spaceBuffer += *scursor++; continue; } // possible start of an encoded word if ( *scursor == '=' ) { QByteArray language; QString decoded; ++scursor; const char *start = scursor; if ( HeaderParsing::parseEncodedWord( scursor, send, decoded, language, usedCS, defaultCS, forceCS ) ) { result += decoded.toUtf8(); onlySpacesSinceLastWord = true; spaceBuffer.clear(); } else { if ( onlySpacesSinceLastWord ) { result += spaceBuffer; onlySpacesSinceLastWord = false; } result += '='; scursor = start; // reset cursor after parsing failure } continue; } else { // unencoded data if ( onlySpacesSinceLastWord ) { result += spaceBuffer; onlySpacesSinceLastWord = false; } result += *scursor; ++scursor; } } return QString::fromUtf8(result); } QString decodeRFC2047String( const QByteArray &src ) { QByteArray usedCS; return decodeRFC2047String( src, usedCS, "utf-8", false ); } QByteArray encodeRFC2047String( const QString &src, const QByteArray &charset, bool addressHeader, bool allow8BitHeaders ) { QByteArray encoded8Bit, result, usedCS; int start=0, end=0; bool nonAscii=false, ok=true, useQEncoding=false; QTextCodec *codec=0; usedCS = charset; codec = KGlobal::charsets()->codecForName( usedCS, ok ); if ( !ok ) { //no codec available => try local8Bit and hope the best ;-) usedCS = KGlobal::locale()->encoding(); codec = KGlobal::charsets()->codecForName( usedCS, ok ); } if ( usedCS.contains( "8859-" ) ) { // use "B"-Encoding for non iso-8859-x charsets useQEncoding = true; } encoded8Bit = codec->fromUnicode( src ); if ( allow8BitHeaders ) { return encoded8Bit; } uint encoded8BitLength = encoded8Bit.length(); for ( unsigned int i=0; i@,.;:\\[]=", encoded8Bit[i] ) != 0 ) ) ) { end = start; // non us-ascii char found, now we determine where to stop encoding nonAscii = true; break; } } if ( nonAscii ) { while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) { // we encode complete words end++; } for ( int x=end; x@,.;:\\[]=",encoded8Bit[x]) != 0 ) ) ) { end = encoded8Bit.length(); // we found another non-ascii word while ( ( end < encoded8Bit.length() ) && ( encoded8Bit[end] != ' ' ) ) { // we encode complete words end++; } } } result = encoded8Bit.left( start ) + "=?" + usedCS; if ( useQEncoding ) { result += "?Q?"; char c, hexcode;// "Q"-encoding implementation described in RFC 2047 for ( int i=start; i= 'a' ) && ( c <= 'z' ) ) || // paranoid mode, encode *all* special chars to avoid problems ( ( c >= 'A' ) && ( c <= 'Z' ) ) || // with "From" & "To" headers ( ( c >= '0' ) && ( c <= '9' ) ) ) { result += c; } else { result += '='; // "stolen" from KMail ;-) hexcode = ((c & 0xF0) >> 4) + 48; if ( hexcode >= 58 ) { hexcode += 7; } result += hexcode; hexcode = (c & 0x0F) + 48; if ( hexcode >= 58 ) { hexcode += 7; } result += hexcode; } } } } else { result += "?B?" + encoded8Bit.mid( start, end - start ).toBase64(); } result +="?="; result += encoded8Bit.right( encoded8Bit.length() - end ); } else { result = encoded8Bit; } return result; } QByteArray uniqueString() { static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; time_t now; char p[11]; int pos, ran; unsigned int timeval; p[10] = '\0'; now = time( 0 ); ran = 1 + (int)(1000.0*rand() / (RAND_MAX + 1.0)); timeval = (now / ran) + getpid(); for ( int i=0; i<10; i++ ) { pos = (int) (61.0*rand() / (RAND_MAX + 1.0)); //kDebug(5320) << pos; p[i] = chars[pos]; } QByteArray ret; ret.setNum( timeval ); ret += '.'; ret += p; return ret; } QByteArray multiPartBoundary() { return "nextPart" + uniqueString(); } QByteArray unfoldHeader( const QByteArray &header ) { QByteArray result; int pos = 0, foldBegin = 0, foldMid = 0, foldEnd = 0; while ( ( foldMid = header.indexOf( '\n', pos ) ) >= 0 ) { foldBegin = foldEnd = foldMid; // find the first space before the line-break while ( foldBegin > 0 ) { if ( !QChar( header[foldBegin - 1] ).isSpace() ) { break; } --foldBegin; } // find the first non-space after the line-break while ( foldEnd <= header.length() - 1 ) { if ( !QChar( header[foldEnd] ).isSpace() ) { break; } ++foldEnd; } result += header.mid( pos, foldBegin - pos ); if ( foldEnd < header.length() -1 ) result += ' '; pos = foldEnd; } result += header.mid( pos, header.length() - pos ); return result; } int indexOfHeader( const QByteArray &src, const QByteArray &name, int &end, int &dataBegin, bool *folded ) { QByteArray n = name; n.append( ':' ); int begin = -1; if ( qstrnicmp( n.constData(), src.constData(), n.length() ) == 0 ) { begin = 0; } else { n.prepend('\n'); const char *p = strcasestr( src.constData(), n.constData() ); if ( !p ) { begin = -1; } else { begin = p - src.constData(); ++begin; } } if ( begin > -1) { //there is a header with the given name dataBegin = begin + name.length() + 1; //skip the name // skip the usual space after the colon if ( src.at( dataBegin ) == ' ' ) { ++dataBegin; } end = dataBegin; int len = src.length() - 1; if ( folded ) *folded = false; if ( src.at(end) != '\n' ) { // check if the header is not empty while ( true ) { end = src.indexOf( '\n', end + 1 ); if ( end == -1 || end == len || ( src[end+1] != ' ' && src[end+1] != '\t' ) ) { //break if we reach the end of the string, honor folded lines break; } else { if ( folded ) *folded = true; } } } if ( end < 0 ) { end = len + 1; //take the rest of the string } return begin; } else { dataBegin = -1; return -1; //header not found } } QByteArray extractHeader( const QByteArray &src, const QByteArray &name ) { int begin, end; bool folded; indexOfHeader( src, name, end, begin, &folded ); if ( begin >= 0 ) { if ( !folded ) { return src.mid( begin, end - begin ); } else { QByteArray hdrValue = src.mid( begin, end - begin ); return unfoldHeader( hdrValue ); } } else { return QByteArray(); //header not found } } QList extractHeaders( const QByteArray &src, const QByteArray &name ) { int begin, end; bool folded; QList result; QByteArray copySrc( src ); indexOfHeader( copySrc, name, end, begin, &folded ); while ( begin >= 0 ) { if ( !folded ) { result.append( copySrc.mid( begin, end - begin ) ); } else { QByteArray hdrValue = copySrc.mid( begin, end - begin ); result.append( unfoldHeader( hdrValue ) ); } // get the next one, a tiny bit ugly, but we don't want the previous to be found again... copySrc = copySrc.mid( end ); indexOfHeader( copySrc, name, end, begin, &folded ); } return result; } void removeHeader( QByteArray &header, const QByteArray &name ) { int begin, end, dummy; begin = indexOfHeader( header, name, end, dummy ); if ( begin >= 0 ) { header.remove( begin, end - begin + 1 ); } } QByteArray CRLFtoLF( const QByteArray &s ) { QByteArray ret = s; ret.replace( "\r\n", "\n" ); return ret; } QByteArray LFtoCRLF( const QByteArray &s ) { QByteArray ret = s; - ret.replace( "\n", "\r\n" ); + ret.replace( '\n', "\r\n" ); return ret; } namespace { template < typename T > void removeQuotesGeneric( T & str ) { bool inQuote = false; for ( int i = 0; i < str.length(); ++i ) { if ( str[i] == '"' ) { str.remove( i, 1 ); i--; inQuote = !inQuote; } else { if ( inQuote && ( str[i] == '\\' ) ) { str.remove( i, 1 ); } } } } } void removeQuots( QByteArray &str ) { removeQuotesGeneric( str ); } void removeQuots( QString &str ) { removeQuotesGeneric( str ); } void addQuotes( QByteArray &str, bool forceQuotes ) { bool needsQuotes=false; for ( int i=0; i < str.length(); i++ ) { if ( strchr("()<>@,.;:[]=\\\"", str[i] ) != 0 ) { needsQuotes = true; } if ( str[i] == '\\' || str[i] == '\"' ) { str.insert( i, '\\' ); i++; } } if ( needsQuotes || forceQuotes ) { str.insert( 0, '\"' ); str.append( "\"" ); } } } // namespace KMime diff --git a/kmime/kmime_util.h b/kmime/kmime_util.h index c57986024..f4eacee4a 100644 --- a/kmime/kmime_util.h +++ b/kmime/kmime_util.h @@ -1,262 +1,262 @@ /* -*- c++ -*- kmime_util.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details 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 __KMIME_UTIL_H__ #define __KMIME_UTIL_H__ #include #include "kmime_export.h" #include "kmime_headers.h" namespace KMime { /** Consult the charset cache. Only used for reducing mem usage by keeping strings in a common repository. @param name */ KMIME_EXPORT extern QByteArray cachedCharset( const QByteArray &name ); /** Consult the language cache. Only used for reducing mem usage by keeping strings in a common repository. @param name */ KMIME_EXPORT extern QByteArray cachedLanguage( const QByteArray &name ); /** Checks whether @p s contains any non-us-ascii characters. @param s */ KMIME_EXPORT extern bool isUsAscii( const QString &s ); /** Returns a user-visible string for a contentEncoding, for example "quoted-printable" for CEquPr. TODO should they be i18n'ed? */ KMIME_EXPORT extern QString nameForEncoding( KMime::Headers::contentEncoding enc ); /** Returns a list of encodings that can correctly encode the @p data. @author Based on KMail code by Thomas McGuire */ KMIME_EXPORT QList encodingsForData( const QByteArray &data ); //@cond PRIVATE extern const uchar specialsMap[16]; extern const uchar tSpecialsMap[16]; extern const uchar aTextMap[16]; extern const uchar tTextMap[16]; extern const uchar eTextMap[16]; inline bool isOfSet( const uchar map[16], unsigned char ch ) { return ( ch < 128 ) && ( map[ ch/8 ] & 0x80 >> ch%8 ); } inline bool isSpecial( char ch ) { return isOfSet( specialsMap, ch ); } inline bool isTSpecial( char ch ) { return isOfSet( tSpecialsMap, ch ); } inline bool isAText( char ch ) { return isOfSet( aTextMap, ch ); } inline bool isTText( char ch ) { return isOfSet( tTextMap, ch ); } inline bool isEText( char ch ) { return isOfSet( eTextMap, ch ); } //@endcond /** Decodes string @p src according to RFC2047,i.e., the construct =?charset?[qb]?encoded?= @param src source string. @param usedCS the detected charset is returned here @param defaultCS the charset to use in case the detected one isn't known to us. @param forceCS force the use of the default charset. @return the decoded string. */ KMIME_EXPORT extern QString decodeRFC2047String( const QByteArray &src, QByteArray &usedCS, const QByteArray &defaultCS = QByteArray(), bool forceCS = false ); /** Decode string @p src according to RFC2047 (ie. the =?charset?[qb]?encoded?= construct). @param src source string. @return the decoded string. */ KMIME_EXPORT extern QString decodeRFC2047String( const QByteArray &src ); /** Encodes string @p src according to RFC2047 using charset @p charset. @param src source string. @param charset charset to use. @param addressHeader if this flag is true, all special chars like <,>,[,],... will be encoded, too. @param allow8bitHeaders if this flag is true, 8Bit headers are allowed. @return the encoded string. */ KMIME_EXPORT extern QByteArray encodeRFC2047String( const QString &src, const QByteArray &charset, bool addressHeader=false, bool allow8bitHeaders=false ); /** Uses current time, pid and random numbers to construct a string that aims to be unique on a per-host basis (ie. for the local part of a message-id or for multipart boundaries. @return the unique string. @see multiPartBoundary */ KMIME_EXPORT extern QByteArray uniqueString(); /** Constructs a random string (sans leading/trailing "--") that can be used as a multipart delimiter (ie. as @p boundary parameter to a multipart/... content-type). @return the randomized string. @see uniqueString */ KMIME_EXPORT extern QByteArray multiPartBoundary(); /** Unfolds the given header if necessary. @param header The header to unfold. */ KMIME_EXPORT extern QByteArray unfoldHeader( const QByteArray &header ); /** Tries to extract the header with name @p name from the string @p src, unfolding it if necessary. @param src the source string. @param name the name of the header to search for. @return the first instance of the header @p name in @p src or a null QCString if no such header was found. */ KMIME_EXPORT extern QByteArray extractHeader( const QByteArray &src, const QByteArray &name ); /** Tries to extract the headers with name @p name from the string @p src, unfolding it if necessary. @param src the source string. @param name the name of the header to search for. @return all instances of the header @p name in @p src @since 4.2 */ KMIME_EXPORT extern QList extractHeaders( const QByteArray &src, const QByteArray &name ); /** Converts all occurrences of "\r\n" (CRLF) in @p s to "\n" (LF). This function is expensive and should be used only if the mail will be stored locally. All decode functions can cope with both line endings. @param s source string containing CRLF's @return the string with CRLF's substitued for LF's @see CRLFtoLF(const char*) LFtoCRLF */ KMIME_EXPORT extern QByteArray CRLFtoLF( const QByteArray &s ); /** Converts all occurrences of "\r\n" (CRLF) in @p s to "\n" (LF). This function is expensive and should be used only if the mail will be stored locally. All decode functions can cope with both line endings. @param s source string containing CRLF's @return the string with CRLF's substitued for LF's @see CRLFtoLF(const QCString&) LFtoCRLF */ KMIME_EXPORT extern QByteArray CRLFtoLF( const char *s ); /** Converts all occurrences of "\n" (LF) in @p s to "\r\n" (CRLF). This function is expensive and should be used only if the mail will be transmitted as an RFC822 message later. All decode functions can cope with and all encode functions can optionally produce both line endings, which is much faster. @param s source string containing CRLF's @return the string with CRLF's substitued for LF's @see CRLFtoLF(const QCString&) LFtoCRLF */ KMIME_EXPORT extern QByteArray LFtoCRLF( const QByteArray &s ); /** Removes quote (DQUOTE) characters and decodes "quoted-pairs" (ie. backslash-escaped characters) @param str the string to work on. @see addQuotes */ KMIME_EXPORT extern void removeQuots( QByteArray &str ); /** Removes quote (DQUOTE) characters and decodes "quoted-pairs" (ie. backslash-escaped characters) @param str the string to work on. @see addQuotes */ KMIME_EXPORT extern void removeQuots( QString &str ); /** Converts the given string into a quoted-string if the string contains any special characters (ie. one of ()<>@,.;:[]=\"). @param str us-ascii string to work on. @param forceQuotes if @p true, always add quote characters. */ KMIME_EXPORT extern void addQuotes( QByteArray &str, bool forceQuotes ); } // namespace KMime #endif /* __KMIME_UTIL_H__ */ diff --git a/kmime/kmime_version.h b/kmime/kmime_version.h index c26350bd5..e61621b7c 100644 --- a/kmime/kmime_version.h +++ b/kmime/kmime_version.h @@ -1,32 +1,32 @@ /* kmime_version.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz See file AUTHORS for details 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 __KMIME_VERSION_H__ #define __KMIME_VERSION_H__ #define KMIME_MAJOR 0; #define KMIME_MINOR 2; #define KMIME_PATCHLEVEL 0; #define KMIME_VERSION (KMIME_MAJOR * 100 + KMIME_MINOR * 10 + KMIME_PATCHLEVEL) #define KMIME_VERSION_STRING "0.2.0" #endif // __KMIME_VERSION_H__ diff --git a/kmime/kmime_warning.h b/kmime/kmime_warning.h index 620a4800f..0f0bd4d16 100644 --- a/kmime/kmime_warning.h +++ b/kmime/kmime_warning.h @@ -1,60 +1,60 @@ /* kmime_warning.h - KMime, the KDE internet mail/usenet news message library. + KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz See file AUTHORS for details 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 KMIME_WARNING_H #define KMIME_WARNING_H #ifndef KMIME_NO_WARNING # include # define KMIME_WARN kWarning(5100) << "Tokenizer Warning:" # define KMIME_WARN_UNKNOWN(x,y) KMIME_WARN << "unknown " #x ": \"" \ << y << "\""; # define KMIME_WARN_UNKNOWN_ENCODING KMIME_WARN << "unknown encoding in " \ "RFC 2047 encoded-word (only know 'q' and 'b')"; # define KMIME_WARN_UNKNOWN_CHARSET(c) KMIME_WARN << "unknown charset \"" \ << c << "\" in RFC 2047 encoded-word"; # define KMIME_WARN_8BIT(ch) KMIME_WARN \ << "8Bit character '" << QString(QChar(ch)) << "'" # define KMIME_WARN_IF_8BIT(ch) if ( (unsigned char)(ch) > 127 ) \ { KMIME_WARN_8BIT(ch); } # define KMIME_WARN_PREMATURE_END_OF(x) KMIME_WARN \ << "Premature end of " #x # define KMIME_WARN_LONE(x) KMIME_WARN << "Lonely " #x " character" # define KMIME_WARN_NON_FOLDING(x) KMIME_WARN << "Non-folding " #x # define KMIME_WARN_CTL_OUTSIDE_QS(x) KMIME_WARN << "Control character " \ #x " outside quoted-string" # define KMIME_WARN_INVALID_X_IN_Y(X,Y) KMIME_WARN << "Invalid character '" \ QString(QChar(X)) << "' in " #Y; # define KMIME_WARN_TOO_LONG(x) KMIME_WARN << #x \ " too long or missing delimiter"; #else # define KMIME_NOP do {} while (0) # define KMIME_WARN_8BIT(ch) KMIME_NOP # define KMIME_WARN_IF_8BIT(ch) KMIME_NOP # define KMIME_WARN_PREMATURE_END_OF(x) KMIME_NOP # define KMIME_WARN_LONE(x) KMIME_NOP # define KMIME_WARN_NON_FOLDING(x) KMIME_NOP # define KMIME_WARN_CTL_OUTSIDE_QS(x) KMIME_NOP #endif #endif diff --git a/kpimutils/linklocator.cpp b/kpimutils/linklocator.cpp index 86496ee27..37338b735 100644 --- a/kpimutils/linklocator.cpp +++ b/kpimutils/linklocator.cpp @@ -1,434 +1,424 @@ /* Copyright (c) 2002 Dave Corrie 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 KDEPIM Utilities library and provides the LinkLocator class. @brief Identifies URLs and email addresses embedded in plaintext. @author Dave Corrie \ */ #include "linklocator.h" #include #include #include #include #include #if KDE_IS_VERSION( 4, 0, 95 ) #include #endif #include #include #include #include #include using namespace KPIMUtils; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class KPIMUtils::LinkLocator::Private { public: int mMaxUrlLen; int mMaxAddressLen; }; //@endcond #if KDE_IS_VERSION( 4, 0, 95 ) // Use a static for this as calls to the KEmoticons constructor are expensive. K_GLOBAL_STATIC( KEmoticons, sEmoticons ) #endif LinkLocator::LinkLocator( const QString &text, int pos ) : mText( text ), mPos( pos ), d( new KPIMUtils::LinkLocator::Private ) { d->mMaxUrlLen = 4096; d->mMaxAddressLen = 255; // If you change either of the above values for maxUrlLen or // maxAddressLen, then please also update the documentation for // setMaxUrlLen()/setMaxAddressLen() in the header file AND the // default values used for the maxUrlLen/maxAddressLen parameters // of convertToHtml(). } LinkLocator::~LinkLocator() { delete d; } void LinkLocator::setMaxUrlLen( int length ) { d->mMaxUrlLen = length; } int LinkLocator::maxUrlLen() const { return d->mMaxUrlLen; } void LinkLocator::setMaxAddressLen( int length ) { d->mMaxAddressLen = length; } int LinkLocator::maxAddressLen() const { return d->mMaxAddressLen; } QString LinkLocator::getUrl() { QString url; if ( atUrl() ) { // for reference: rfc1738: // Thus, only alphanumerics, the special characters "$-_.+!*'(),", and // reserved characters used for their reserved purposes may be used // unencoded within a URL. // NOTE: this implementation is not RFC conforming int start = mPos; while ( mPos < (int)mText.length() && mText[mPos] > ' ' && mText[mPos] != '"' && QString( "<>[]" ).indexOf( mText[mPos] ) == -1 ) { ++mPos; } // some URLs really end with: # / & - _ const QString allowedSpecialChars = QString( "#/&-_" ); while ( mPos > start && mText[mPos-1].isPunct() && allowedSpecialChars.indexOf( mText[mPos-1] ) == -1 ) { --mPos; } url = mText.mid( start, mPos - start ); if ( isEmptyUrl(url) || mPos - start > maxUrlLen() ) { mPos = start; url = ""; } else { --mPos; } } return url; } // keep this in sync with KMMainWin::slotUrlClicked() bool LinkLocator::atUrl() const { // the following characters are allowed in a dot-atom (RFC 2822): // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" ); // the character directly before the URL must not be a letter, a number or // any other character allowed in a dot-atom (RFC 2822). if ( ( mPos > 0 ) && ( mText[mPos-1].isLetterOrNumber() || ( allowedSpecialChars.indexOf( mText[mPos-1] ) != -1 ) ) ) { return false; } QChar ch = mText[mPos]; return ( ch == 'h' && ( mText.mid( mPos, 7 ) == "http://" || mText.mid( mPos, 8 ) == "https://" ) ) || ( ch == 'v' && mText.mid( mPos, 6 ) == "vnc://" ) || ( ch == 'f' && ( mText.mid( mPos, 7 ) == "fish://" || mText.mid( mPos, 6 ) == "ftp://" || mText.mid( mPos, 7 ) == "ftps://" ) ) || ( ch == 's' && ( mText.mid( mPos, 7 ) == "sftp://" || mText.mid( mPos, 6 ) == "smb://" ) ) || ( ch == 'm' && mText.mid( mPos, 7 ) == "mailto:" ) || ( ch == 'w' && mText.mid( mPos, 4 ) == "www." ) || ( ch == 'f' && ( mText.mid( mPos, 4 ) == "ftp." || mText.mid( mPos, 7 ) == "file://" ) ) || ( ch == 'n' && mText.mid( mPos, 5 ) == "news:" ); } bool LinkLocator::isEmptyUrl( const QString &url ) const { return url.isEmpty() || url == "http://" || url == "https://" || url == "fish://" || url == "ftp://" || url == "ftps://" || url == "sftp://" || url == "smb://" || url == "vnc://" || url == "mailto" || url == "www" || url == "ftp" || url == "news" || url == "news://"; } QString LinkLocator::getEmailAddress() { QString address; if ( mText[mPos] == '@' ) { // the following characters are allowed in a dot-atom (RFC 2822): // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ const QString allowedSpecialChars = QString( ".!#$%&'*+-/=?^_`{|}~" ); // determine the local part of the email address int start = mPos - 1; while ( start >= 0 && mText[start].unicode() < 128 && ( mText[start].isLetterOrNumber() || mText[start] == '@' || // allow @ to find invalid email addresses allowedSpecialChars.indexOf( mText[start] ) != -1 ) ) { if ( mText[start] == '@' ) { return QString(); // local part contains '@' -> no email address } --start; } ++start; // we assume that an email address starts with a letter or a digit while ( ( start < mPos ) && !mText[start].isLetterOrNumber() ) { ++start; } if ( start == mPos ) { return QString(); // local part is empty -> no email address } // determine the domain part of the email address int dotPos = INT_MAX; int end = mPos + 1; while ( end < (int)mText.length() && ( mText[end].isLetterOrNumber() || mText[end] == '@' || // allow @ to find invalid email addresses mText[end] == '.' || mText[end] == '-' ) ) { if ( mText[end] == '@' ) { return QString(); // domain part contains '@' -> no email address } if ( mText[end] == '.' ) { dotPos = qMin( dotPos, end ); // remember index of first dot in domain } ++end; } // we assume that an email address ends with a letter or a digit while ( ( end > mPos ) && !mText[end - 1].isLetterOrNumber() ) { --end; } if ( end == mPos ) { return QString(); // domain part is empty -> no email address } if ( dotPos >= end ) { return QString(); // domain part doesn't contain a dot } if ( end - start > maxAddressLen() ) { return QString(); // too long -> most likely no email address } address = mText.mid( start, end - start ); mPos = end - 1; } return address; } QString LinkLocator::convertToHtml( const QString &plainText, int flags, int maxUrlLen, int maxAddressLen ) { LinkLocator locator( plainText ); locator.setMaxUrlLen( maxUrlLen ); locator.setMaxAddressLen( maxAddressLen ); QString str; QString result( (QChar*)0, (int)locator.mText.length() * 2 ); QChar ch; int x; bool startOfLine = true; QString emoticon; for ( locator.mPos = 0, x = 0; locator.mPos < (int)locator.mText.length(); locator.mPos++, x++ ) { ch = locator.mText[locator.mPos]; if ( flags & PreserveSpaces ) { if ( ch == ' ' ) { if ( startOfLine ) { - result += " "; - locator.mPos++, x++; startOfLine = false; } - while ( locator.mText[locator.mPos] == ' ' ) { - result += ' '; - locator.mPos++, x++; - if ( locator.mText[locator.mPos] == ' ' ) { - result += " "; - locator.mPos++, x++; - } - } - locator.mPos--, x--; + result += " "; continue; } else if ( ch == '\t' ) { do { result += " "; x++; } while ( ( x & 7 ) != 0 ); x--; startOfLine = false; continue; } } if ( ch == '\n' ) { result += "
      \n"; // Keep the \n, so apps can figure out the quoting levels correctly. startOfLine = true; x = -1; continue; } startOfLine = false; if ( ch == '&' ) { result += "&"; } else if ( ch == '"' ) { result += """; } else if ( ch == '<' ) { result += "<"; } else if ( ch == '>' ) { result += ">"; } else { const int start = locator.mPos; if ( !( flags & IgnoreUrls ) ) { str = locator.getUrl(); if ( !str.isEmpty() ) { QString hyperlink; if ( str.left( 4 ) == "www." ) { hyperlink = "http://" + str; } else if ( str.left( 4 ) == "ftp." ) { hyperlink = "ftp://" + str; } else { hyperlink = str; } str = str.replace( '&', "&" ); result += "" + str + ""; x += locator.mPos - start; continue; } str = locator.getEmailAddress(); if ( !str.isEmpty() ) { // len is the length of the local part int len = str.indexOf( '@' ); QString localPart = str.left( len ); // remove the local part from the result (as '&'s have been expanded to // & we have to take care of the 4 additional characters per '&') result.truncate( result.length() - len - ( localPart.count( '&' ) * 4 ) ); x -= len; result += "" + str + ""; x += str.length() - 1; continue; } } if ( flags & HighlightText ) { str = locator.highlightedText(); if ( !str.isEmpty() ) { result += str; x += locator.mPos - start; continue; } } result += ch; } } #if KDE_IS_VERSION( 4, 0, 95 ) if ( flags & ReplaceSmileys ) { QStringList exclude; exclude << "(c)" << "(C)" << ">:-(" << ">:(" << "(B)" << "(b)" << "(P)" << "(p)"; exclude << "(O)" << "(o)" << "(D)" << "(d)" << "(E)" << "(e)" << "(K)" << "(k)"; exclude << "(I)" << "(i)" << "(L)" << "(l)" << "(8)" << "(T)" << "(t)" << "(G)"; exclude << "(g)" << "(F)" << "(f)" << "(H)"; exclude << "8)" << "(N)" << "(n)" << "(Y)" << "(y)" << "(U)" << "(u)" << "(W)" << "(w)"; static QString cachedEmoticonsThemeName; if ( cachedEmoticonsThemeName.isEmpty() ) { cachedEmoticonsThemeName = KEmoticons::currentThemeName(); } result = sEmoticons->theme( cachedEmoticonsThemeName ).parseEmoticons( result, KEmoticonsTheme::StrictParse | KEmoticonsTheme::SkipHTML, exclude ); } #endif return result; } QString LinkLocator::pngToDataUrl( const QString &iconPath ) { if ( iconPath.isEmpty() ) { return QString(); } QFile pngFile( iconPath ); if ( !pngFile.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) ) { return QString(); } QByteArray ba = pngFile.readAll(); pngFile.close(); return QString::fromLatin1( "data:image/png;base64,%1" ).arg( ba.toBase64().constData() ); } QString LinkLocator::highlightedText() { // formating symbols must be prepended with a whitespace if ( ( mPos > 0 ) && !mText[mPos-1].isSpace() ) { return QString(); } const QChar ch = mText[mPos]; if ( ch != '/' && ch != '*' && ch != '_' ) { return QString(); } QRegExp re = QRegExp( QString( "\\%1([0-9A-Za-z]+)\\%2" ).arg( ch ).arg( ch ) ); if ( re.indexIn( mText, mPos ) == mPos ) { int length = re.matchedLength(); // there must be a whitespace after the closing formating symbol if ( mPos + length < mText.length() && !mText[mPos + length].isSpace() ) { return QString(); } mPos += length - 1; switch ( ch.toLatin1() ) { case '*': return "" + re.cap( 1 ) + ""; case '_': return "" + re.cap( 1 ) + ""; case '/': return "" + re.cap( 1 ) + ""; } } return QString(); } diff --git a/kpimutils/tests/testemail.cpp b/kpimutils/tests/testemail.cpp index 093268637..fd46be831 100644 --- a/kpimutils/tests/testemail.cpp +++ b/kpimutils/tests/testemail.cpp @@ -1,546 +1,549 @@ /* This file is part of the KDE project Copyright (C) 2004 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //krazy:excludeall=contractions #include "kpimutils/email.h" #include #include #include #include #include #include using namespace KPIMUtils; static bool check( const QString &txt, const QString &a, const QString &b ) { if ( a == b ) { kDebug() << txt << ": checking '" << a << "' against expected value '" << b << "'..." << "ok"; } else { kDebug() << txt << ": checking '" << a << "' against expected value '" << b << "'..." << "KO !"; exit( 1 ); } return true; } static bool check( const QString &txt, const QStringList &a, const QStringList &b ) { if ( a.join( "\n" ) == b.join( "\n" ) ) { kDebug() << txt << ": checking list [" << a.join( ", " ) << "] against expected value [" << b.join( ", " ) << "]..." << "ok"; } else { kDebug() << txt << " : checking list [" << a.join( ",\n" ) << "] against expected value [" << b.join( ",\n" ) << "]..." << "KO !"; exit(1); } return true; } static bool checkGetNameAndEmail( const QString &input, const QString &expName, const QString &expEmail, bool expRetVal ) { QString name, email; bool retVal = extractEmailAddressAndName( input, email, name ); check( "getNameAndMail " + input + " retVal", retVal?QString::fromLatin1( "true" ):QString::fromLatin1( "false" ), expRetVal?QString::fromLatin1( "true" ):QString::fromLatin1( "false" ) ); check( "getNameAndMail " + input + " name", name, expName ); check( "getNameAndMail " + input + " email", email, expEmail ); return true; } static QString emailTestParseResultToString( EmailParseResult errorCode ) { if( errorCode == TooManyAts ) { return "TooManyAts"; } else if( errorCode == TooFewAts ) { return "TooFewAts"; } else if( errorCode == AddressEmpty ) { return "AddressEmpty"; } else if( errorCode == MissingLocalPart ) { return "MissingLocalPart"; } else if( errorCode == MissingDomainPart ) { return "MissingDomainPart"; } else if( errorCode == UnbalancedParens ) { return "UnbalancedParens"; } else if( errorCode == AddressOk ) { return "AddressOk"; } else if( errorCode == UnclosedAngleAddr ) { return "UnclosedAngleAddr"; } else if( errorCode == UnexpectedEnd ) { return "UnexpectedEnd"; } else if( errorCode == UnopenedAngleAddr ) { return "UnopenedAngleAddr"; } else if( errorCode == DisallowedChar ) { return "DisallowedChar"; } else if( errorCode == UnexpectedComma ) { return "UnexpectedComma"; } else if( errorCode == UnbalancedQuote ) { return "UnbalancedQuote"; } else if( errorCode == InvalidDisplayName ) { return "InvalidDisplayName"; } return "unknown error code"; } static QString simpleEmailTestParseResultToString( bool validEmail ) { if ( validEmail ) { return "true"; } return "false"; } static bool checkIsValidEmailAddress( const QString &input, const QString & expErrorCode ) { EmailParseResult errorCode = isValidAddress( input ); QString errorC = emailTestParseResultToString( errorCode ); check( "isValidEmailAddress " + input + " errorCode ", errorC, expErrorCode ); return true; } static bool checkIsValidSimpleEmailAddress( const QString &input, const QString &expResult ) { bool validEmail = isValidSimpleAddress( input ); QString result = simpleEmailTestParseResultToString( validEmail ); check( "isValidSimpleEmailAddress " + input + " result ", result, expResult ); return true; } static bool checkGetEmailAddress( const QString &input, const QString &expResult ) { QString emailAddress = extractEmailAddress( input ); QString result = emailAddress; check( "getEmailAddress " + input + " result ", result, expResult ); return true; } static bool checkSplitEmailAddrList( const QString &input, const QStringList &expResult ) { QStringList emailAddresses = splitAddressList( input ); check( "splitEmailAddrList( \"" + input + "\" ) result ", emailAddresses, expResult ); return true; } static bool checkNormalizeAddressesAndEncodeIDNs( const QString &input, const QString &expResult ) { QString result = normalizeAddressesAndEncodeIdn( input ); check( "normalizeAddressesAndEncodeIDNs( \"" + input + "\" ) result ", result, expResult ); return true; } static bool checkNormalizeAddressesAndDecodeIDNs( const QString &input, const QString &expResult ) { QString result = normalizeAddressesAndDecodeIdn( input ); check( "normalizeAddressesAndDecodeIDNs( \"" + input + "\" ) result ", result, expResult ); return true; } static bool checkQuoteIfNecessary( const QString &input, const QString &expResult ) { QString result = quoteNameIfNecessary( input ); check( "quoteNameIfNecessary " + input + " result ", result, expResult ); return true; } int main( int argc, char *argv[] ) { QCoreApplication app( argc, argv ); // Empty input checkGetNameAndEmail( QString(), QString(), QString(), false ); // Email only checkGetNameAndEmail( "faure@kde.org", QString(), "faure@kde.org", false ); // Normal case checkGetNameAndEmail( "David Faure ", "David Faure", "faure@kde.org", true ); // Double-quotes checkGetNameAndEmail( "\"Faure, David\" ", "Faure, David", "faure@kde.org", true ); checkGetNameAndEmail( " \"David Faure\"", "David Faure", "faure@kde.org", true ); // Parenthesis checkGetNameAndEmail( "faure@kde.org (David Faure)", "David Faure", "faure@kde.org", true ); checkGetNameAndEmail( "(David Faure) faure@kde.org", "David Faure", "faure@kde.org", true ); checkGetNameAndEmail( "My Name (me) ", "My Name (me)", "me@home.net", true ); // #93513 // Nested parenthesis as per https://intevation.de/roundup/kolab/issue858 checkGetNameAndEmail( "faure@kde.org (David (The Man) Faure)", "David (The Man) Faure", "faure@kde.org", true ); // Double-quotes inside parenthesis checkGetNameAndEmail( "faure@kde.org (David \"Crazy\" Faure)", "David \"Crazy\" Faure", "faure@kde.org", true ); checkGetNameAndEmail( "(David \"Crazy\" Faure) faure@kde.org", "David \"Crazy\" Faure", "faure@kde.org", true ); // Parenthesis inside double-quotes checkGetNameAndEmail( "\"Faure (David)\" ", "Faure (David)", "faure@kde.org", true ); checkGetNameAndEmail( " \"Faure (David)\"", "Faure (David)", "faure@kde.org", true ); // Space in email checkGetNameAndEmail( "David Faure < faure@kde.org >", "David Faure", "faure@kde.org", true ); // Check that '@' in name doesn't confuse it checkGetNameAndEmail( "faure@kde.org (a@b)", "a@b", "faure@kde.org", true ); // Interestingly, this isn't supported. //checkGetNameAndEmail( "\"a@b\" ", "a@b", "faure@kde.org", true ); // While typing, when there's no '@' yet checkGetNameAndEmail( "foo", "foo", QString(), false ); checkGetNameAndEmail( "foo <", "foo", QString(), false ); checkGetNameAndEmail( "foo , KHZ ", "Faure, David", "faure@kde.org", true ); // domain literals also need to work checkGetNameAndEmail( "Matt Douhan ", "Matt Douhan", "matt@[123.123.123.123]", true ); // @ inside the comment checkGetNameAndEmail( "\"Matt@Douhan\" ", "Matt@Douhan", "matt@fruitsalad.org", true ); // No '@' checkGetNameAndEmail( "foo ", "foo", "distlist", true ); // To many @'s checkIsValidEmailAddress( "matt@@fruitsalad.org", "TooManyAts" ); // To few @'s checkIsValidEmailAddress( "mattfruitsalad.org", "TooFewAts" ); // An empty string checkIsValidEmailAddress( QString(), "AddressEmpty" ); // email address starting with a @ checkIsValidEmailAddress( "@mattfruitsalad.org", "MissingLocalPart" ); // make sure that starting @ and an additional @ in the same email address don't conflict // trap the starting @ first and break checkIsValidEmailAddress( "@matt@fruitsalad.org", "MissingLocalPart" ); // email address ending with a @ checkIsValidEmailAddress( "mattfruitsalad.org@", "MissingDomainPart" ); // make sure that ending with@ and an additional @ in the email address don't conflict checkIsValidEmailAddress( "matt@fruitsalad.org@", "MissingDomainPart" ); // unbalanced Parens checkIsValidEmailAddress( "mattjongel)@fruitsalad.org", "UnbalancedParens" ); // unbalanced Parens the other way around checkIsValidEmailAddress( "mattjongel(@fruitsalad.org", "UnbalancedParens" ); // Correct parens just to make sure it works checkIsValidEmailAddress( "matt(jongel)@fruitsalad.org", "AddressOk" ); // Check that anglebrackets are closed checkIsValidEmailAddress( "matt douhanmatt@fruitsalad.org", "UnopenedAngleAddr" ); // Check that angle brackets are closed the other way around, and anglebrackets in domainpart // instead of local part checkIsValidEmailAddress( "matt douhan matt@", "AddressOk" ); // a full email address with comments angle brackets and the works should be valid too checkIsValidEmailAddress( "Matt (jongel) Douhan ", "AddressOk" ); // Double quotes checkIsValidEmailAddress( "\"Matt Douhan\" ", "AddressOk" ); // Double quotes inside parens checkIsValidEmailAddress( "Matt (\"jongel\") Douhan ", "AddressOk" ); // DOuble quotes not closed checkIsValidEmailAddress( "Matt \"jongel Douhan ", "UnbalancedQuote" ); // Parens inside double quotes checkIsValidEmailAddress( "Matt \"(jongel)\" Douhan ", "AddressOk" ); // Space in email checkIsValidEmailAddress( "Matt Douhan < matt@fruitsalad.org >", "AddressOk" ); // @ is allowed inisde doublequotes checkIsValidEmailAddress( "\"matt@jongel\" ", "AddressOk" ); // anglebrackets inside dbl quotes checkIsValidEmailAddress( "\"matt\" ", "AddressOk" ); // a , inside a double quoted string is OK, how do I know this? well Ingo says so // and it makes sense since it is also a separator of email addresses checkIsValidEmailAddress( "\"Douhan, Matt\" ", "AddressOk" ); // Domains literals also need to work checkIsValidEmailAddress( "Matt Douhan ", "AddressOk" ); // Typo in domain literal address checkIsValidEmailAddress( "Matt Douhan ", "UnexpectedComma" ); // Some more insane tests but still valid so they must work checkIsValidEmailAddress( "Matt Douhan <\"m@att\"@jongel.com>", "AddressOk" ); // BUG 99657 checkIsValidEmailAddress( "matt@jongel.fibbel.com", "AddressOk" ); // BUG 98720 checkIsValidEmailAddress( "mailto:@mydomain", "DisallowedChar" ); // correct error msg when a comma is inside <> checkIsValidEmailAddress( "Matt Douhan ", "UnexpectedComma" ); //several commentlevels checkIsValidEmailAddress( "Matt Douhan (hey(jongel)fibbel) ", "AddressOk" ); // several comment levels and one (the outer) being unbalanced checkIsValidEmailAddress( "Matt Douhan (hey(jongel)fibbel ", "UnbalancedParens" ); // several comment levels and one (the inner) being unbalanced checkIsValidEmailAddress( "Matt Douhan (hey(jongelfibbel) ", "UnbalancedParens" ); // an error inside a double quote is no error checkIsValidEmailAddress ( "Matt Douhan \"(jongel\" ", "AddressOk" ); // inside a quoted string double quotes are only allowed in pairs as per rfc2822 checkIsValidEmailAddress( "Matt Douhan \"jongel\"fibbel\" ", "UnbalancedQuote" ); // a questionmark is valid in an atom checkIsValidEmailAddress ( "Matt? ", "AddressOk" ); // weird but OK checkIsValidEmailAddress( "\"testing, \\\"testing\" ", "AddressOk" ); // escape a quote to many to see if it makes it invalid checkIsValidEmailAddress( "\"testing, \\\"testing\\\" ", "UnbalancedQuote" ); // escape a parens and thus make a comma appear checkIsValidEmailAddress( "Matt (jongel, fibbel\\) ", "UnbalancedParens" ); // several errors inside doublequotes checkIsValidEmailAddress( "Matt \"(jongel,\\\" < fibbel\\)\" ", "AddressOk" ); // BUG 105705 checkIsValidEmailAddress( "matt-@fruitsalad.org", "AddressOk" ); // underscore at the end of local part checkIsValidEmailAddress( "matt_@fruitsalad.org", "AddressOk" ); // how about ( comment ) in the domain part checkIsValidEmailAddress( "matt_@(this is a cool host)fruitsalad.org", "AddressOk" ); // To quote rfc2822 the test below is aesthetically displeasing, but perfectly legal. checkIsValidEmailAddress( "Pete(A wonderful \\) chap) ", "AddressOk" ); // quoted pair or not quoted pair checkIsValidEmailAddress( "\"jongel '\\\" fibbel\" ", "AddressOk" ); checkIsValidEmailAddress( "\"jongel '\" fibbel\" ", "UnbalancedQuote" ); // full atext support according to rfc2822 checkIsValidEmailAddress( "!matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "#matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "$matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "%matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "&matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "'matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "*matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "+matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "/matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "=matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "?matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "^matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "_matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "-matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "`matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "{matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "|matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "}matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "~matt@fruitsalad.org", "AddressOk" ); checkIsValidEmailAddress( "matt%matt@fruitsalad.org", "AddressOk" ); //bug 105405 checkIsValidEmailAddress( "[foobar] ", "InvalidDisplayName" ); checkIsValidEmailAddress( "matt \"[foobar]\" Douhan ", "AddressOk" ); checkIsValidEmailAddress( "Matt Douhan ", "TooFewAts" ); // checks for "pure" email addresses in the form of xxx@yyy.tld checkIsValidSimpleEmailAddress( "matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( QString::fromUtf8( "test@tรคst.invalid" ), "true" ); // non-ASCII char as first char of IDN checkIsValidSimpleEmailAddress( QString::fromUtf8( "i_want@รธl.invalid" ), "true" ); checkIsValidSimpleEmailAddress( "matt@[123.123.123.123]", "true" ); checkIsValidSimpleEmailAddress( "matt@[3.3.3.3]", "true" ); checkIsValidSimpleEmailAddress( "matt@[4.4.4.4]", "true" ); checkIsValidSimpleEmailAddress( "matt@[192.168.254.254]", "true" ); checkIsValidSimpleEmailAddress( "\"matt\"@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "-matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "\"-matt\"@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "matt@jongel.fibbel.com", "true" ); checkIsValidSimpleEmailAddress( "Matt Douhan ", "false" ); // BUG 105705 checkIsValidSimpleEmailAddress( "matt-@fibbel.com", "true" ); checkIsValidSimpleEmailAddress( "matt@fibbel-is-a-geek.com", "true" ); checkIsValidSimpleEmailAddress( "matt_@fibbel.com", "true" ); // Check the defined chars for atext according to rfc2822 checkIsValidSimpleEmailAddress( "!matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "#matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "$matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "%matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "&matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "'matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "*matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "+matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "/matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "=matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "?matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "^matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "_matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "-matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "`matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "{matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "|matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "}matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "~matt@fruitsalad.org", "true" ); // BUG 108476 checkIsValidSimpleEmailAddress( "foo+matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "bar=matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "jongel-matt@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "matt-@fruitsalad.org", "true" ); // check if the pure email address is wrong checkIsValidSimpleEmailAddress( "mattfruitsalad.org", "false" ); checkIsValidSimpleEmailAddress( "matt@[123.123.123.123", "false" ); checkIsValidSimpleEmailAddress( "matt@123.123.123.123]", "false" ); checkIsValidSimpleEmailAddress( "\"matt@fruitsalad.org", "false" ); checkIsValidSimpleEmailAddress( "matt\"@fruitsalad.org", "false" ); checkIsValidSimpleEmailAddress( QString(), "false" ); + // BUG 203881 + checkIsValidSimpleEmailAddress( "2advance@my-site.com", "true" ); + // and here some insane but still valid cases checkIsValidSimpleEmailAddress( "\"m@tt\"@fruitsalad.org", "true" ); checkIsValidSimpleEmailAddress( "matt\"@@\"fruitsalad.org", "false" ); // check the getEmailAddress address method checkGetEmailAddress( "matt@fruitsalad.org", "matt@fruitsalad.org" ); checkGetEmailAddress( "Matt Douhan ", "matt@fruitsalad.org" ); checkGetEmailAddress( "\"Matt Douhan \" ", "matt@fruitsalad.org" ); checkGetEmailAddress( "\"Matt \" ", "matt@fruitsalad.org" ); checkGetEmailAddress( "Matt Douhan (jongel) ", "matt@fruitsalad.org" ); checkGetEmailAddress( "\"Douhan, Matt\" ", "matt@fruitsalad.org" ); checkGetEmailAddress( "\"Matt Douhan (m@tt)\" ", "matt@fruitsalad.org" ); checkGetEmailAddress( "\"Matt Douhan\" (matt ", QString() ); checkGetEmailAddress( "Matt Douhan ", "matt@[123.123.123.123]" ); // check the splitEmailAddrList method checkSplitEmailAddrList( "kloecker@kde.org (Kloecker, Ingo)", QStringList() << "kloecker@kde.org (Kloecker, Ingo)" ); checkSplitEmailAddrList( "Matt Douhan , Foo Bar ", QStringList() << "Matt Douhan " << "Foo Bar " ); checkSplitEmailAddrList( "\"Matt, Douhan\" , Foo Bar ", QStringList() << "\"Matt, Douhan\" " << "Foo Bar " ); // check checkNormalizeAddressesAndEncodeIDNs checkNormalizeAddressesAndEncodeIDNs( "matt@fruitsalad.org", "matt@fruitsalad.org" ); checkNormalizeAddressesAndEncodeIDNs( "Matt Douhan ", "Matt Douhan " ); checkNormalizeAddressesAndEncodeIDNs( "Matt Douhan (jongel) ", "Matt Douhan (jongel) " ); checkNormalizeAddressesAndEncodeIDNs( "Matt Douhan (jongel,fibbel) ", "Matt Douhan (jongel,fibbel) " ); checkNormalizeAddressesAndEncodeIDNs( "matt@fruitsalad.org (jongel,fibbel)", "\"jongel,fibbel\" " ); checkNormalizeAddressesAndEncodeIDNs( "matt@fruitsalad.org (\"jongel,fibbel\")", "\"jongel,fibbel\" " ); // check checkNormalizeAddressesAndDecodeIDNs checkNormalizeAddressesAndDecodeIDNs( "=?us-ascii?Q?Surname=2C=20Name?= ", "\"Surname, Name\" " ); checkNormalizeAddressesAndDecodeIDNs( "=?iso-8859-1?B?5Hf8b2xmLPZBbmRyZWFz?= ", QString::fromUtf8( "\"รคwรผolf,รถAndreas\" " ) ); checkNormalizeAddressesAndDecodeIDNs( QString::fromUtf8( "\"Andreas StraรŸ\" " ), QString::fromUtf8( "\"Andreas StraรŸ\" " ) ); checkNormalizeAddressesAndDecodeIDNs( QString::fromUtf8( "\"Andrรกs\" \"Manลฃia\" " ), QString::fromUtf8( "\"Andrรกs\" \"Manลฃia\" " ) ); // check the "quote if necessary" method checkQuoteIfNecessary( "Matt Douhan", "Matt Douhan" ); checkQuoteIfNecessary( "Douhan, Matt", "\"Douhan, Matt\"" ); checkQuoteIfNecessary( "Matt \"jongel\" Douhan", "\"Matt \\\"jongel\\\" Douhan\"" ); checkQuoteIfNecessary( "Matt \\\"jongel\\\" Douhan", "\"Matt \\\"jongel\\\" Douhan\"" ); checkQuoteIfNecessary( "trailing '\\\\' should never occur \\", "\"trailing '\\\\' should never occur \\\"" ); checkQuoteIfNecessary( "\"don't quote again\"", "\"don't quote again\"" ); checkQuoteIfNecessary( "\"leading double quote", "\"\\\"leading double quote\"" ); checkQuoteIfNecessary( "trailing double quote\"", "\"trailing double quote\\\"\"" ); printf( "\nTest OK !\n" ); return 0; } diff --git a/kpimutils/tests/testlinklocator.cpp b/kpimutils/tests/testlinklocator.cpp index 258a85fb2..9f53eed50 100644 --- a/kpimutils/tests/testlinklocator.cpp +++ b/kpimutils/tests/testlinklocator.cpp @@ -1,196 +1,222 @@ /* This file is part of the kpimutils library. Copyright (C) 2005 Ingo Kloecker Copyright (C) 2007 Allen Winter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "testlinklocator.h" #include "testlinklocator.moc" QTEST_KDEMAIN( LinkLocatorTest, NoGUI ) #include "kpimutils/linklocator.h" using namespace KPIMUtils; void LinkLocatorTest::testGetEmailAddress() { // empty input const QString emptyQString; LinkLocator ll1( emptyQString, 0 ); QVERIFY( ll1.getEmailAddress().isEmpty() ); // no '@' at scan position LinkLocator ll2( "foo@bar.baz", 0 ); QVERIFY( ll2.getEmailAddress().isEmpty() ); // '@' in local part LinkLocator ll3( "foo@bar@bar.baz", 7 ); QVERIFY( ll3.getEmailAddress().isEmpty() ); // empty local part LinkLocator ll4( "@bar.baz", 0 ); QVERIFY( ll4.getEmailAddress().isEmpty() ); LinkLocator ll5( ".@bar.baz", 1 ); QVERIFY( ll5.getEmailAddress().isEmpty() ); LinkLocator ll6( " @bar.baz", 1 ); QVERIFY( ll6.getEmailAddress().isEmpty() ); LinkLocator ll7( ".!#$%&'*+-/=?^_`{|}~@bar.baz", strlen( ".!#$%&'*+-/=?^_`{|}~" ) ); QVERIFY( ll7.getEmailAddress().isEmpty() ); // allowed special chars in local part of address LinkLocator ll8( "a.!#$%&'*+-/=?^_`{|}~@bar.baz", strlen( "a.!#$%&'*+-/=?^_`{|}~" ) ); QVERIFY( ll8.getEmailAddress() == "a.!#$%&'*+-/=?^_`{|}~@bar.baz" ); // '@' in domain part LinkLocator ll9 ( "foo@bar@bar.baz", 3 ); QVERIFY( ll9.getEmailAddress().isEmpty() ); // domain part without dot LinkLocator lla( "foo@bar", 3 ); QVERIFY( lla.getEmailAddress().isEmpty() ); LinkLocator llb( "foo@bar.", 3 ); QVERIFY( llb.getEmailAddress().isEmpty() ); LinkLocator llc( ".foo@bar", 4 ); QVERIFY( llc.getEmailAddress().isEmpty() ); LinkLocator lld( "foo@bar ", 3 ); QVERIFY( lld.getEmailAddress().isEmpty() ); LinkLocator lle( " foo@bar", 4 ); QVERIFY( lle.getEmailAddress().isEmpty() ); LinkLocator llf( "foo@bar-bar", 3 ); QVERIFY( llf.getEmailAddress().isEmpty() ); // empty domain part LinkLocator llg( "foo@", 3 ); QVERIFY( llg.getEmailAddress().isEmpty() ); LinkLocator llh( "foo@.", 3 ); QVERIFY( llh.getEmailAddress().isEmpty() ); LinkLocator lli( "foo@-", 3 ); QVERIFY( lli.getEmailAddress().isEmpty() ); // simple address LinkLocator llj( "foo@bar.baz", 3 ); QVERIFY( llj.getEmailAddress() == "foo@bar.baz" ); LinkLocator llk( "foo@bar.baz.", 3 ); QVERIFY( llk.getEmailAddress() == "foo@bar.baz" ); LinkLocator lll( ".foo@bar.baz", 4 ); QVERIFY( lll.getEmailAddress() == "foo@bar.baz" ); LinkLocator llm( "foo@bar.baz-", 3 ); QVERIFY( llm.getEmailAddress() == "foo@bar.baz" ); LinkLocator lln( "-foo@bar.baz", 4 ); QVERIFY( lln.getEmailAddress() == "foo@bar.baz" ); LinkLocator llo( "foo@bar.baz ", 3 ); QVERIFY( llo.getEmailAddress() == "foo@bar.baz" ); LinkLocator llp( " foo@bar.baz", 4 ); QVERIFY( llp.getEmailAddress() == "foo@bar.baz" ); LinkLocator llq( "foo@bar-bar.baz", 3 ); QVERIFY( llq.getEmailAddress() == "foo@bar-bar.baz" ); } void LinkLocatorTest::testGetUrl() { QStringList brackets; brackets << "" << ""; // no brackets brackets << "(" << ")"; brackets << "<" << ">"; brackets << "[" << "]"; brackets << "" << ""; for (int i = 0; i < brackets.count(); i += 2) testGetUrl2(brackets[i], brackets[i+1]); } void LinkLocatorTest::testGetUrl2(const QString &left, const QString &right) { QStringList schemas; schemas << "http://"; schemas << "https://"; schemas << "vnc://"; schemas << "fish://"; schemas << "ftp://"; schemas << "ftps://"; schemas << "sftp://"; schemas << "smb://"; schemas << "file://"; QStringList urls; urls << "www.kde.org"; urls << "user@www.kde.org"; urls << "user:pass@www.kde.org"; urls << "user:pass@www.kde.org:1234"; urls << "user:pass@www.kde.org:1234/sub/path"; urls << "user:pass@www.kde.org:1234/sub/path?a=1"; urls << "user:pass@www.kde.org:1234/sub/path?a=1#anchor"; urls << "user:pass@www.kde.org:1234/sub/path/special(123)?a=1#anchor"; urls << "user:pass@www.kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor"; foreach (QString schema, schemas) { foreach (QString url, urls) { QString test(left + schema + url + right); LinkLocator ll(test, left.length()); QString gotUrl = ll.getUrl(); bool ok = ( gotUrl == (schema + url) ); qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << (schema + url); QVERIFY2( ok, qPrintable(test) ); } } QStringList urlsWithoutSchema; urlsWithoutSchema << ".kde.org"; urlsWithoutSchema << ".kde.org:1234/sub/path"; urlsWithoutSchema << ".kde.org:1234/sub/path?a=1"; urlsWithoutSchema << ".kde.org:1234/sub/path?a=1#anchor"; urlsWithoutSchema << ".kde.org:1234/sub/path/special(123)?a=1#anchor"; urlsWithoutSchema << ".kde.org:1234/sub/path:with:colon/special(123)?a=1#anchor"; QStringList starts; starts << "www" << "ftp" << "news:www"; foreach (QString start, starts) { foreach (QString url, urlsWithoutSchema) { QString test(left + start + url + right); LinkLocator ll(test, left.length()); QString gotUrl = ll.getUrl(); bool ok = ( gotUrl == (start + url) ); qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << (start + url); QVERIFY2( ok, qPrintable(test) ); } } // mailto { QString addr = "mailto:test@kde.org"; QString test(left + addr + right); LinkLocator ll(test, left.length()); QString gotUrl = ll.getUrl(); bool ok = ( gotUrl == addr ); qDebug() << "check:" << (ok ? "OK" : "NOK") << test << "=>" << addr; QVERIFY2( ok, qPrintable(test) ); } } + +void LinkLocatorTest::testHtmlConvert_data() +{ + QTest::addColumn("plainText"); + QTest::addColumn("flags"); + QTest::addColumn("htmlText"); + + QTest::newRow( "" ) << "foo" << 0 << "foo"; + QTest::newRow( "" ) << " foo " << 0 << " foo "; + // Linker error when using PreserveSpaces, therefore the hardcoded 0x01 + QTest::newRow( "" ) << " foo" << 0x01 << " foo"; + QTest::newRow( "" ) << " foo" << 0x01 << "  foo"; + QTest::newRow( "" ) << " foo " << 0x01 << "  foo  "; +} + +void LinkLocatorTest::testHtmlConvert() +{ + QFETCH(QString, plainText); + QFETCH(int, flags); + QFETCH(QString, htmlText); + + QString actualHtml = LinkLocator::convertToHtml( plainText, flags ); + QCOMPARE( actualHtml, htmlText ); +} + + diff --git a/kpimutils/tests/testlinklocator.h b/kpimutils/tests/testlinklocator.h index 65544eeab..cfaaf01cd 100644 --- a/kpimutils/tests/testlinklocator.h +++ b/kpimutils/tests/testlinklocator.h @@ -1,38 +1,40 @@ /* This file is part of the kpimutils library. Copyright (c) 2007 Allen Winter 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 TESTLINKLOCATOR_H #define TESTLINKLOCATOR_H #include class LinkLocatorTest : public QObject { Q_OBJECT private Q_SLOTS: void testGetEmailAddress(); void testGetUrl(); + void testHtmlConvert(); + void testHtmlConvert_data(); private: void testGetUrl2(const QString &left, const QString &right); }; #endif diff --git a/mailtransport/dispatcherinterface.cpp b/mailtransport/dispatcherinterface.cpp index 2965a8c4c..8b02e8eb4 100644 --- a/mailtransport/dispatcherinterface.cpp +++ b/mailtransport/dispatcherinterface.cpp @@ -1,118 +1,118 @@ /* Copyright (c) 2009 Constantin Berzan 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 "dispatcherinterface.h" #include "outboxactions.h" #include #include #include #include #include #include #include using namespace Akonadi; using namespace MailTransport; /** @internal */ class MailTransport::DispatcherInterfacePrivate { public: DispatcherInterfacePrivate(); ~DispatcherInterfacePrivate(); DispatcherInterface *instance; // slots void massModifyResult( KJob *job ); }; K_GLOBAL_STATIC( DispatcherInterfacePrivate, sInstance ) DispatcherInterfacePrivate::DispatcherInterfacePrivate() : instance( new DispatcherInterface( this ) ) { } DispatcherInterfacePrivate::~DispatcherInterfacePrivate() { delete instance; } void DispatcherInterfacePrivate::massModifyResult( KJob *job ) { // Nothing to do here, really. If the job fails, the user can retry it. if( job->error() ) { kDebug() << "failed" << job->errorString(); } else { kDebug() << "succeeded."; } } - - DispatcherInterface::DispatcherInterface( DispatcherInterfacePrivate *dd ) - : QObject() - , d( dd ) + : QObject(), d( dd ) { } DispatcherInterface *DispatcherInterface::self() { return sInstance->instance; } AgentInstance DispatcherInterface::dispatcherInstance() const { - AgentInstance a = AgentManager::self()->instance( QLatin1String( "akonadi_maildispatcher_agent" ) ); + AgentInstance a = + AgentManager::self()->instance( QLatin1String( "akonadi_maildispatcher_agent" ) ); if( !a.isValid() ) { kWarning() << "Could not get MDA instance."; } return a; } void DispatcherInterface::dispatchManually() { if( !LocalFolders::self()->isReady() ) { kWarning() << "LocalFolders not ready."; return; } - FilterActionJob *mjob = new FilterActionJob( LocalFolders::self()->outbox(), new SendQueuedAction, this ); + FilterActionJob *mjob = + new FilterActionJob( LocalFolders::self()->outbox(), new SendQueuedAction, this ); connect( mjob, SIGNAL(result(KJob*)), this, SLOT(massModifyResult(KJob*)) ); } void DispatcherInterface::retryDispatching() { if( !LocalFolders::self()->isReady() ) { kWarning() << "LocalFolders not ready."; return; } - FilterActionJob *mjob = new FilterActionJob( LocalFolders::self()->outbox(), new ClearErrorAction, this ); + FilterActionJob *mjob = + new FilterActionJob( LocalFolders::self()->outbox(), new ClearErrorAction, this ); connect( mjob, SIGNAL(result(KJob*)), this, SLOT(massModifyResult(KJob*)) ); } #include "dispatcherinterface.moc" diff --git a/mailtransport/dispatchmodeattribute.cpp b/mailtransport/dispatchmodeattribute.cpp index e7320a268..8e59ecb61 100644 --- a/mailtransport/dispatchmodeattribute.cpp +++ b/mailtransport/dispatchmodeattribute.cpp @@ -1,106 +1,106 @@ /* Copyright 2009 Constantin Berzan 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 "dispatchmodeattribute.h" #include #include "akonadi/attributefactory.h" using namespace Akonadi; using namespace MailTransport; class DispatchModeAttribute::Private { public: DispatchMode mMode; QDateTime mDueDate; }; DispatchModeAttribute::DispatchModeAttribute( DispatchMode mode, const QDateTime &date ) : d( new Private ) { d->mMode = mode; d->mDueDate = date; } DispatchModeAttribute::~DispatchModeAttribute() { delete d; } -DispatchModeAttribute* DispatchModeAttribute::clone() const +DispatchModeAttribute *DispatchModeAttribute::clone() const { return new DispatchModeAttribute( d->mMode, d->mDueDate ); } QByteArray DispatchModeAttribute::type() const { static const QByteArray sType( "DispatchModeAttribute" ); return sType; } QByteArray DispatchModeAttribute::serialized() const { switch( d->mMode ) { case Immediately: return "immediately"; case AfterDueDate: return "after" + d->mDueDate.toString(Qt::ISODate).toLatin1(); case Never: return "never"; } Q_ASSERT( false ); return QByteArray(); // suppress control-reaches-end-of-non-void-function warning } void DispatchModeAttribute::deserialize( const QByteArray &data ) { d->mDueDate = QDateTime(); if ( data == "immediately" ) { d->mMode = Immediately; } else if ( data == "never" ) { d->mMode = Never; } else if ( data.startsWith( QByteArray( "after" ) ) ) { d->mMode = AfterDueDate; d->mDueDate = QDateTime::fromString( QString::fromLatin1( data.mid(5) ), Qt::ISODate ); // NOTE: 5 is the strlen of "after". } else { kWarning() << "Failed to deserialize data [" << data << "]"; } } DispatchModeAttribute::DispatchMode DispatchModeAttribute::dispatchMode() const { return d->mMode; } void DispatchModeAttribute::setDispatchMode( DispatchMode mode ) { d->mMode = mode; } - + QDateTime DispatchModeAttribute::dueDate() const { return d->mDueDate; } void DispatchModeAttribute::setDueDate( const QDateTime &date ) { d->mDueDate = date; } diff --git a/mailtransport/dispatchmodeattribute.h b/mailtransport/dispatchmodeattribute.h index 6bd9e636e..7c03a24a2 100644 --- a/mailtransport/dispatchmodeattribute.h +++ b/mailtransport/dispatchmodeattribute.h @@ -1,101 +1,101 @@ /* Copyright 2009 Constantin Berzan 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_DISPATCHMODEATTRIBUTE_H #define MAILTRANSPORT_DISPATCHMODEATTRIBUTE_H #include #include #include namespace MailTransport { /** Attribute determining how and when a message from the outbox should be dispatched. Messages can be sent immediately, sent only when the user explicitly requests it, or sent automatically at a certain date and time. @author Constantin Berzan @since 4.4 */ class MAILTRANSPORT_EXPORT DispatchModeAttribute : public Akonadi::Attribute { public: /** Determines how the message is sent. */ - enum DispatchMode - { + enum DispatchMode { Immediately, ///< Send message as soon as possible. AfterDueDate, ///< Send message at a certain date/time. Never ///< Send message only when the user requests so. }; /** Creates a new DispatchModeAttribute. */ - explicit DispatchModeAttribute( DispatchMode mode = Immediately, const QDateTime &date = QDateTime() ); + explicit DispatchModeAttribute( DispatchMode mode = Immediately, + const QDateTime &date = QDateTime() ); /** Destroys the DispatchModeAttribute. */ virtual ~DispatchModeAttribute(); /* reimpl */ - virtual DispatchModeAttribute* clone() const; + virtual DispatchModeAttribute *clone() const; virtual QByteArray type() const; virtual QByteArray serialized() const; virtual void deserialize( const QByteArray &data ); /** Returns the dispatch mode for the message. @see DispatchMode. */ DispatchMode dispatchMode() const; /** Sets the dispatch mode for the message. @see DispatchMode. */ void setDispatchMode( DispatchMode mode ); /** Returns the date and time when the message should be sent. Only valid if dispatchMode() is AfterDueDate. */ QDateTime dueDate() const; /** Sets the date and time when the message should be sent. Make sure you set the DispatchMode to AfterDueDate first. @see setDispatchMode. */ void setDueDate( const QDateTime &date ); private: class Private; Private *const d; }; } // namespace MailTransport #endif // MAILTRANSPORT_DISPATCHMODEATTRIBUTE_H diff --git a/mailtransport/errorattribute.cpp b/mailtransport/errorattribute.cpp index e2393cd4d..a35f9500f 100644 --- a/mailtransport/errorattribute.cpp +++ b/mailtransport/errorattribute.cpp @@ -1,76 +1,76 @@ /* Copyright 2009 Constantin Berzan 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 "errorattribute.h" #include #include using namespace Akonadi; using namespace MailTransport; class ErrorAttribute::Private { public: QString mMessage; }; ErrorAttribute::ErrorAttribute( const QString &msg ) : d( new Private ) { d->mMessage = msg; } ErrorAttribute::~ErrorAttribute() { delete d; } -ErrorAttribute* ErrorAttribute::clone() const +ErrorAttribute *ErrorAttribute::clone() const { return new ErrorAttribute( d->mMessage ); } QByteArray ErrorAttribute::type() const { static const QByteArray sType( "ErrorAttribute" ); return sType; } QByteArray ErrorAttribute::serialized() const { return d->mMessage.toUtf8(); } void ErrorAttribute::deserialize( const QByteArray &data ) { d->mMessage = QString::fromUtf8( data ); } QString ErrorAttribute::message() const { return d->mMessage; } void ErrorAttribute::setMessage( const QString &msg ) { d->mMessage = msg; } diff --git a/mailtransport/errorattribute.h b/mailtransport/errorattribute.h index 26e42c510..37dda6270 100644 --- a/mailtransport/errorattribute.h +++ b/mailtransport/errorattribute.h @@ -1,75 +1,75 @@ /* Copyright 2009 Constantin Berzan 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_ERRORATTRIBUTE_H #define MAILTRANSPORT_ERRORATTRIBUTE_H #include #include #include namespace MailTransport { /** Attribute given to the messages that failed to be sent. Contains the error message encountered. @author Constantin Berzan @since 4.4 */ class MAILTRANSPORT_EXPORT ErrorAttribute : public Akonadi::Attribute { public: /** Creates a new ErrorAttribute. */ ErrorAttribute( const QString &msg = QString() ); /** Destroys this ErrorAttribute. */ virtual ~ErrorAttribute(); /* reimpl */ - virtual ErrorAttribute* clone() const; + virtual ErrorAttribute *clone() const; virtual QByteArray type() const; virtual QByteArray serialized() const; virtual void deserialize( const QByteArray &data ); /** Returns the i18n'ed error message. */ QString message() const; /** Sets the error message. */ void setMessage( const QString &msg ); private: class Private; Private *const d; }; } // namespace MailTransport #endif // MAILTRANSPORT_ERRORATTRIBUTE_H diff --git a/mailtransport/mailtransport_export.h b/mailtransport/mailtransport_export.h index 49c366f58..3f1e6104c 100644 --- a/mailtransport/mailtransport_export.h +++ b/mailtransport/mailtransport_export.h @@ -1,51 +1,50 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MAILTRANSPORT_MAILTRANSPORT_EXPORT_H #define MAILTRANSPORT_MAILTRANSPORT_EXPORT_H #include #ifndef MAILTRANSPORT_EXPORT # if defined(MAKE_MAILTRANSPORT_LIB) /* We are building this library */ # define MAILTRANSPORT_EXPORT KDE_EXPORT # else /* We are using this library */ # define MAILTRANSPORT_EXPORT KDE_IMPORT # endif #endif // TODO KDE5: Get rid of all this. #ifndef MAILTRANSPORT_DEPRECATED # if defined( USES_DEPRECATED_MAILTRANSPORT_API ) /* Avoid deprecated warnings from ourselves and the MDA. */ # define MAILTRANSPORT_DEPRECATED # else /* Show deprecated warnings for anyone else. */ # define MAILTRANSPORT_DEPRECATED KDE_DEPRECATED # endif #endif #ifndef MAILTRANSPORT_EXPORT_DEPRECATED # define MAILTRANSPORT_EXPORT_DEPRECATED MAILTRANSPORT_DEPRECATED MAILTRANSPORT_EXPORT #endif - #endif diff --git a/mailtransport/messagequeuejob.cpp b/mailtransport/messagequeuejob.cpp index 0e4c5f733..9f3ae344c 100644 --- a/mailtransport/messagequeuejob.cpp +++ b/mailtransport/messagequeuejob.cpp @@ -1,290 +1,287 @@ /* Copyright (c) 2009 Constantin Berzan 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 "messagequeuejob.h" #include "transport.h" #include "transportattribute.h" #include "transportmanager.h" #include #include #include #include #include #include #include using namespace Akonadi; using namespace KMime; using namespace MailTransport; /** @internal */ class MailTransport::MessageQueueJob::Private { public: Private( MessageQueueJob *qq ) : q( qq ) { transport = -1; dispatchMode = DispatchModeAttribute::Immediately; sentBehaviour = SentBehaviourAttribute::MoveToDefaultSentCollection; moveToCollection = -1; started = false; } MessageQueueJob *const q; Message::Ptr message; int transport; DispatchModeAttribute::DispatchMode dispatchMode; QDateTime dueDate; SentBehaviourAttribute::SentBehaviour sentBehaviour; Collection::Id moveToCollection; QString from; QStringList to; QStringList cc; QStringList bcc; bool started; /** Returns true if this message has everything it needs and is ready to be sent. */ bool validate(); // slot void doStart(); }; bool MessageQueueJob::Private::validate() { if( !message ) { q->setError( UserDefinedError ); q->setErrorText( i18n( "Empty message." ) ); q->emitResult(); return false; } if( to.count() + cc.count() + bcc.count() == 0 ) { q->setError( UserDefinedError ); q->setErrorText( i18n( "Message has no recipients." ) ); q->emitResult(); return false; } if( dispatchMode == DispatchModeAttribute::AfterDueDate && !dueDate.isValid() ) { q->setError( UserDefinedError ); q->setErrorText( i18n( "Message has invalid due date." ) ); q->emitResult(); return false; } if( TransportManager::self()->transportById( transport, false ) == 0 ) { q->setError( UserDefinedError ); q->setErrorText( i18n( "Message has invalid transport." ) ); q->emitResult(); return false; } if( sentBehaviour == SentBehaviourAttribute::MoveToCollection && moveToCollection < 0 ) { q->setError( UserDefinedError ); q->setErrorText( i18n( "Message has invalid sent-mail folder." ) ); q->emitResult(); return false; } else if( sentBehaviour == SentBehaviourAttribute::MoveToDefaultSentCollection ) { Q_ASSERT( LocalFolders::self()->isReady() ); Q_ASSERT( LocalFolders::self()->sentMail().isValid() ); } return true; // all ok } void MessageQueueJob::Private::doStart() { LocalFolders::self()->disconnect( q ); Q_ASSERT( !started ); started = true; if( !validate() ) { // The error has been set; the result has been emitted. return; } // Create item. Item item; item.setMimeType( QLatin1String( "message/rfc822" ) ); item.setPayload( message ); // Set attributes. AddressAttribute *addrA = new AddressAttribute( from, to, cc, bcc ); DispatchModeAttribute *dmA = new DispatchModeAttribute( dispatchMode, dueDate ); SentBehaviourAttribute *sA = new SentBehaviourAttribute( sentBehaviour, moveToCollection ); TransportAttribute *tA = new TransportAttribute( transport ); item.addAttribute( addrA ); item.addAttribute( dmA ); item.addAttribute( sA ); item.addAttribute( tA ); // Set flags. item.setFlag( "queued" ); // Store the item in the outbox. Q_ASSERT( LocalFolders::self()->isReady() ); Collection col = LocalFolders::self()->outbox(); ItemCreateJob *job = new ItemCreateJob( item, col ); // job autostarts q->addSubjob( job ); } - - MessageQueueJob::MessageQueueJob( QObject *parent ) - : KCompositeJob( parent ) - , d( new Private( this ) ) + : KCompositeJob( parent ), d( new Private( this ) ) { } MessageQueueJob::~MessageQueueJob() { delete d; } Message::Ptr MessageQueueJob::message() const { return d->message; } int MessageQueueJob::transportId() const { return d->transport; } DispatchModeAttribute::DispatchMode MessageQueueJob::dispatchMode() const { return d->dispatchMode; } QDateTime MessageQueueJob::sendDueDate() const { if( d->dispatchMode != DispatchModeAttribute::AfterDueDate ) { kWarning() << "Called when dispatchMode is not AfterDueDate."; } return d->dueDate; } Collection::Id MessageQueueJob::moveToCollection() const { if( d->sentBehaviour != SentBehaviourAttribute::MoveToCollection ) { kWarning() << "Called when sentBehaviour is not MoveToCollection."; } return d->moveToCollection; } QString MessageQueueJob::from() const { return d->from; } QStringList MessageQueueJob::to() const { return d->to; } QStringList MessageQueueJob::cc() const { return d->cc; } QStringList MessageQueueJob::bcc() const { return d->bcc; } void MessageQueueJob::setMessage( Message::Ptr message ) { d->message = message; } void MessageQueueJob::setTransportId( int id ) { d->transport = id; } void MessageQueueJob::setDispatchMode( DispatchModeAttribute::DispatchMode mode ) { d->dispatchMode = mode; } void MessageQueueJob::setDueDate( const QDateTime &date ) { d->dueDate = date; } void MessageQueueJob::setSentBehaviour( SentBehaviourAttribute::SentBehaviour beh ) { d->sentBehaviour = beh; } void MessageQueueJob::setMoveToCollection( Collection::Id cid ) { d->moveToCollection = cid; } void MessageQueueJob::setFrom( const QString &from ) { d->from = from; } void MessageQueueJob::setTo( const QStringList &to ) { d->to = to; } void MessageQueueJob::setCc( const QStringList &cc ) { d->cc = cc; } void MessageQueueJob::setBcc( const QStringList &bcc ) { d->bcc = bcc; } void MessageQueueJob::start() { LocalFolders *folders = LocalFolders::self(); connect( folders, SIGNAL( foldersReady() ), this, SLOT( doStart() ) ); folders->fetch(); // will emit foldersReady() } void MessageQueueJob::slotResult( KJob *job ) { // error handling KCompositeJob::slotResult( job ); if( !error() ) { emitResult(); } } #include "messagequeuejob.moc" diff --git a/mailtransport/messagequeuejob.h b/mailtransport/messagequeuejob.h index ee337a44d..ae8e2ec48 100644 --- a/mailtransport/messagequeuejob.h +++ b/mailtransport/messagequeuejob.h @@ -1,255 +1,255 @@ /* Copyright (c) 2009 Constantin Berzan 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_MESSAGEQUEUEJOB_H #define MAILTRANSPORT_MESSAGEQUEUEJOB_H #include #include "dispatchmodeattribute.h" #include "sentbehaviourattribute.h" #include #include #include #include #include #include #include namespace MailTransport { /** @short Provides an interface for sending email. This class takes a KMime::Message and some related info such as sender and recipient addresses, and places the message in the outbox. The mail dispatcher agent will then take it from there and send it. This is the preferred way for applications to send email. This job requires some options to be set before being started. These are setMessage, setTransportId, setFrom, and one of setTo, setCc, or setBcc. Other settings are optional: setDispatchMode, setSentBehaviour. Example: @code MessageQueueJob *job = new MessageQueueJob( this ); job->setMessage( msg ); // msg is a Message::Ptr job->setTransportId( TransportManager::self()->defaultTransportId() ); // Use the default dispatch mode. // Use the default sent-behaviour. job->setFrom( from ); // from is a QString job->setTo( to ); // to is a QStringList connect( job, SIGNAL(result(KJob*)), this, SLOT(jobResult(KJob*)) ); job->start(); @endcode @see DispatchModeAttribute @see SentBehaviourAttribute @author Constantin Berzan @since 4.4 */ class MAILTRANSPORT_EXPORT MessageQueueJob : public KCompositeJob { Q_OBJECT public: /** Creates a new MessageQueueJob. This is not an autostarting job; you need to call start() yourself. */ explicit MessageQueueJob( QObject *parent = 0 ); - + /** Destroys the MessageQueueJob. This job deletes itself after finishing. */ virtual ~MessageQueueJob(); /** Returns the message to be sent. */ KMime::Message::Ptr message() const; /** Returns the transport id to use for sending the message. @see TransportManager. */ int transportId() const; /** Returns the dispatch mode for this message. @see DispatchModeAttribute. */ DispatchModeAttribute::DispatchMode dispatchMode() const; /** Returns the date and time when this message should be sent. Only valid if dispatchMode() is AfterDueDate. @see DispatchModeAttribute. */ QDateTime sendDueDate() const; /** Returns the sent-behaviour of this message. This determines what will happen to the message after it is sent. @see SentBehaviourAttribute. */ SentBehaviourAttribute::SentBehaviour sentBehaviour() const; /** Returns the collection to which the message will be moved after it is sent. Only valid if sentBehaviour() is MoveToCollection. @see SentBehaviourAttribute. */ Akonadi::Collection::Id moveToCollection() const; /** Returns the address of the sender. */ QString from() const; /** Returns the addresses of the "To:" receivers. */ QStringList to() const; /** Returns the addresses of the "Cc:" receivers. */ QStringList cc() const; /** Returns the addresses of the "Bcc:" receivers. */ QStringList bcc() const; /** Sets the message to be sent. */ void setMessage( KMime::Message::Ptr message ); /** Sets the transport id to use for sending the message. If you want to use the default transport, you must specify so explicitly: @code job->setTransportId( TransportManager::self()->defaultTransportId() ); @endcode @see TransportManager. */ void setTransportId( int id ); /** Sets the dispatch mode for this message. The default dispatch mode is Immediately (meaning the message will be sent as soon as possible). @see DispatchModeAttribute. */ void setDispatchMode( DispatchModeAttribute::DispatchMode mode ); /** Sets the date and time when this message should be sent. @code job->setDispatchMode( DispatchModeAttribute::AfterDueDate ); job->setDueDate( ... ); @endcode @see DispatchModeAttribute. */ void setDueDate( const QDateTime &date ); /** Sets the sent-behaviour of this message. This determines what will happen to the message after it is sent. The default sent-behaviour is MoveToDefaultSentCollection, which moves the message to the default sent-mail collection. @see SentBehaviourAttribute. */ void setSentBehaviour( SentBehaviourAttribute::SentBehaviour beh ); /** Sets the collection to which the message will be moved after it is sent. @code job->setSentBehaviour( SentBehaviourAttribute::MoveToCollection ); job->setMoveToCollection( ... ); @endcode @see SentBehaviourAttribute. */ void setMoveToCollection( Akonadi::Collection::Id cid ); /** Sets the address of the sender. */ void setFrom( const QString &from ); /** Sets the addresses of the "To:" receivers." */ void setTo( const QStringList &to ); /** Sets the addresses of the "Cc:" receivers." */ void setCc( const QStringList &cc ); /** Sets the addresses of the "Bcc:" receivers." */ void setBcc( const QStringList &bcc ); /** Creates the item and places it in the outbox. It is now queued for sending by the mail dispatcher agent. */ virtual void start(); protected Q_SLOTS: /** Called when the ItemCreateJob subjob finishes. (reimplemented from KCompositeJob) */ virtual void slotResult( KJob * ); private: class Private; friend class Private; Private *const d; Q_PRIVATE_SLOT( d, void doStart() ) }; } // namespace MailTransport #endif // MAILTRANSPORT_MESSAGEQUEUEJOB_H diff --git a/mailtransport/outboxactions.cpp b/mailtransport/outboxactions.cpp index 01a73858d..35c6e9524 100644 --- a/mailtransport/outboxactions.cpp +++ b/mailtransport/outboxactions.cpp @@ -1,106 +1,104 @@ /* Copyright (c) 2009 Constantin Berzan 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 "outboxactions.h" #include "dispatchmodeattribute.h" #include "errorattribute.h" #include using namespace Akonadi; using namespace MailTransport; class MailTransport::SendQueuedAction::Private { }; SendQueuedAction::SendQueuedAction() : d( new Private ) { } SendQueuedAction::~SendQueuedAction() { delete d; } ItemFetchScope SendQueuedAction::fetchScope() const { ItemFetchScope scope; scope.fetchFullPayload( false ); scope.fetchAttribute(); return scope; } bool SendQueuedAction::itemAccepted( const Item &item ) const { if( !item.hasAttribute() ) { kWarning() << "Item doesn't have DispatchModeAttribute."; return false; } return item.attribute()->dispatchMode() == DispatchModeAttribute::Never; } Job *SendQueuedAction::itemAction( const Item &item ) const { Item cp = item; cp.addAttribute( new DispatchModeAttribute ); // defaults to Immediately return new ItemModifyJob( cp ); } - - class MailTransport::ClearErrorAction::Private { }; ClearErrorAction::ClearErrorAction() : d( new Private ) { } ClearErrorAction::~ClearErrorAction() { delete d; } ItemFetchScope ClearErrorAction::fetchScope() const { ItemFetchScope scope; scope.fetchFullPayload( false ); scope.fetchAttribute(); return scope; } bool ClearErrorAction::itemAccepted( const Item &item ) const { return item.hasAttribute(); } Job *ClearErrorAction::itemAction( const Item &item ) const { Item cp = item; cp.removeAttribute(); cp.clearFlag( "error" ); cp.setFlag( "queued" ); return new ItemModifyJob( cp ); } diff --git a/mailtransport/outboxactions.h b/mailtransport/outboxactions.h index ab9fb83db..ea574d158 100644 --- a/mailtransport/outboxactions.h +++ b/mailtransport/outboxactions.h @@ -1,102 +1,101 @@ /* Copyright (c) 2009 Constantin Berzan 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_OUTBOXACTIONS_H #define MAILTRANSPORT_OUTBOXACTIONS_H #include #include #include #include namespace MailTransport { /** FilterAction that finds all messages with a DispatchMode of Never and assigns them a DispatchMode of Immediately. This is used to send "queued" messages on demand. @see FilterActionJob @author Constantin Berzan @since 4.4 */ class MAILTRANSPORT_EXPORT SendQueuedAction : public Akonadi::FilterAction { public: /** Creates a SendQueuedAction. */ SendQueuedAction(); /** Destroys this object. */ virtual ~SendQueuedAction(); /* reimpl */ virtual Akonadi::ItemFetchScope fetchScope() const; /* reimpl */ virtual bool itemAccepted( const Akonadi::Item &item ) const; /* reimpl */ virtual Akonadi::Job *itemAction( const Akonadi::Item &item ) const; private: class Private; Private *const d; }; - /** FilterAction that finds all messages with an ErrorAttribute, removes the attribute, and sets the "queued" flag. This is used to retry sending messages that failed. @see FilterActionJob @author Constantin Berzan @since 4.4 */ class MAILTRANSPORT_EXPORT ClearErrorAction : public Akonadi::FilterAction { public: /** Creates a ClearErrorAction. */ ClearErrorAction(); /** Destroys this object. */ virtual ~ClearErrorAction(); /* reimpl */ virtual Akonadi::ItemFetchScope fetchScope() const; /* reimpl */ virtual bool itemAccepted( const Akonadi::Item &item ) const; /* reimpl */ virtual Akonadi::Job *itemAction( const Akonadi::Item &item ) const; private: class Private; Private *const d; }; } // namespace MailTransport #endif // MAILTRANSPORT_OUTBOXACTIONS_H diff --git a/mailtransport/resourcesendjob.cpp b/mailtransport/resourcesendjob.cpp index 192b8c77f..b53e00cc2 100644 --- a/mailtransport/resourcesendjob.cpp +++ b/mailtransport/resourcesendjob.cpp @@ -1,96 +1,95 @@ /* Copyright (c) 2009 Constantin Berzan 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 "resourcesendjob.h" #include "messagequeuejob.h" #include "transport.h" #include #include #include #include #include #include #include #include #include #include #include - using namespace Akonadi; using namespace KMime; using namespace MailTransport; /** * Private class that helps to provide binary compatibility between releases. * @internal */ class MailTransport::ResourceSendJobPrivate { public: ResourceSendJobPrivate( ResourceSendJob *qq ) : q( qq ) { } void slotEmitResult(); // slot ResourceSendJob *const q; }; void ResourceSendJobPrivate::slotEmitResult() { // KCompositeJob took care of the error. q->emitResult(); } ResourceSendJob::ResourceSendJob( Transport *transport, QObject *parent ) : TransportJob( transport, parent ), d( new ResourceSendJobPrivate( this ) ) { } ResourceSendJob::~ResourceSendJob() { delete d; } void ResourceSendJob::doStart() { Message::Ptr msg = Message::Ptr( new Message ); msg->setContent( data() ); MessageQueueJob *job = new MessageQueueJob; job->setMessage( msg ); job->setTransportId( transport()->id() ); // Default dispatch mode (send now). // Move to default sent-mail collection. job->setFrom( sender() ); job->setTo( to() ); job->setCc( cc() ); job->setBcc( bcc() ); addSubjob( job ); // Once the item is in the outbox, there is nothing more we can do. connect( job, SIGNAL(result(KJob*)), this, SLOT(slotEmitResult()) ); job->start(); } #include "resourcesendjob.moc" diff --git a/mailtransport/sentbehaviourattribute.cpp b/mailtransport/sentbehaviourattribute.cpp index 42e35e7de..6c072a0ce 100644 --- a/mailtransport/sentbehaviourattribute.cpp +++ b/mailtransport/sentbehaviourattribute.cpp @@ -1,106 +1,106 @@ /* Copyright 2009 Constantin Berzan 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 "sentbehaviourattribute.h" #include #include using namespace Akonadi; using namespace MailTransport; class SentBehaviourAttribute::Private { public: SentBehaviour mBehaviour; Akonadi::Collection::Id mMoveToCollection; }; SentBehaviourAttribute::SentBehaviourAttribute( SentBehaviour beh, Collection::Id moveToCollection ) : d( new Private ) { d->mBehaviour = beh; d->mMoveToCollection = moveToCollection; } SentBehaviourAttribute::~SentBehaviourAttribute() { delete d; } -SentBehaviourAttribute* SentBehaviourAttribute::clone() const +SentBehaviourAttribute *SentBehaviourAttribute::clone() const { return new SentBehaviourAttribute( d->mBehaviour, d->mMoveToCollection ); } QByteArray SentBehaviourAttribute::type() const { static const QByteArray sType( "SentBehaviourAttribute" ); return sType; } QByteArray SentBehaviourAttribute::serialized() const { switch( d->mBehaviour ) { case Delete: return "delete"; case MoveToCollection: return "moveTo" + QByteArray::number( d->mMoveToCollection ); case MoveToDefaultSentCollection: return "moveToDefault"; } Q_ASSERT( false ); return QByteArray(); } void SentBehaviourAttribute::deserialize( const QByteArray &data ) { d->mMoveToCollection = -1; if ( data == "delete" ) { d->mBehaviour = Delete; } else if ( data == "moveToDefault" ) { d->mBehaviour = MoveToDefaultSentCollection; } else if ( data.startsWith( QByteArray( "moveTo" ) ) ) { d->mBehaviour = MoveToCollection; d->mMoveToCollection = data.mid(6).toLongLong(); // NOTE: 6 is the strlen of "moveTo". } else { Q_ASSERT( false ); } } SentBehaviourAttribute::SentBehaviour SentBehaviourAttribute::sentBehaviour() const { return d->mBehaviour; } void SentBehaviourAttribute::setSentBehaviour( SentBehaviour beh ) { d->mBehaviour = beh; } Collection::Id SentBehaviourAttribute::moveToCollection() const { return d->mMoveToCollection; } void SentBehaviourAttribute::setMoveToCollection( Collection::Id moveToCollection ) { d->mMoveToCollection = moveToCollection; } diff --git a/mailtransport/sentbehaviourattribute.h b/mailtransport/sentbehaviourattribute.h index 3f784774d..aac5801fb 100644 --- a/mailtransport/sentbehaviourattribute.h +++ b/mailtransport/sentbehaviourattribute.h @@ -1,101 +1,100 @@ /* Copyright 2009 Constantin Berzan 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_SENTBEHAVIOURATTRIBUTE_H #define MAILTRANSPORT_SENTBEHAVIOURATTRIBUTE_H #include #include #include namespace MailTransport { /** Attribute determining what will happen to a message after it is sent. The message can be deleted from the Outbox, moved to the default sent-mail collection, or moved to a custom collection. @author Constantin Berzan @since 4.4 */ class MAILTRANSPORT_EXPORT SentBehaviourAttribute : public Akonadi::Attribute { public: /** What to do with the item in the outbox after it has been sent successfully. */ - enum SentBehaviour - { + enum SentBehaviour { Delete, ///< Delete the item from the outbox. MoveToCollection, ///< Move the item to the default sent-mail collection. MoveToDefaultSentCollection ///< Move the item to a custom collection. }; /** Creates a new SentBehaviourAttribute. */ explicit SentBehaviourAttribute( SentBehaviour beh = MoveToDefaultSentCollection, Akonadi::Collection::Id moveToCollection = -1 ); /** Destroys the SentBehaviourAttribute. */ virtual ~SentBehaviourAttribute(); /* reimpl */ - virtual SentBehaviourAttribute* clone() const; + virtual SentBehaviourAttribute *clone() const; virtual QByteArray type() const; virtual QByteArray serialized() const; virtual void deserialize( const QByteArray &data ); /** Returns the sent-behaviour of the message. @see SentBehaviour. */ SentBehaviour sentBehaviour() const; /** Sets the sent-behaviour of the message. @see SentBehaviour. */ void setSentBehaviour( SentBehaviour beh ); /** Returns the collection to which the item should be moved after it is sent. Only valid if sentBehaviour() is MoveToCollection. */ Akonadi::Collection::Id moveToCollection() const; - + /** Sets the collection to which the item should be moved after it is sent. Make sure you set the SentBehaviour to MoveToCollection first. @see setSentBehaviour. */ void setMoveToCollection( Akonadi::Collection::Id moveToCollection ); private: class Private; Private *const d; }; } // namespace MailTransport #endif // MAILTRANSPORT_SENTBEHAVIOURATTRIBUTE_H diff --git a/mailtransport/transportattribute.cpp b/mailtransport/transportattribute.cpp index 1e738ba13..c8771cd27 100644 --- a/mailtransport/transportattribute.cpp +++ b/mailtransport/transportattribute.cpp @@ -1,81 +1,81 @@ /* Copyright 2009 Constantin Berzan 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 "transportattribute.h" #include "transportmanager.h" #include using namespace Akonadi; using namespace MailTransport; class TransportAttribute::Private { public: int mId; }; TransportAttribute::TransportAttribute( int id ) : d( new Private ) { d->mId = id; } TransportAttribute::~TransportAttribute() { delete d; } -TransportAttribute* TransportAttribute::clone() const +TransportAttribute *TransportAttribute::clone() const { return new TransportAttribute( d->mId ); } QByteArray TransportAttribute::type() const { static const QByteArray sType( "TransportAttribute" ); return sType; } QByteArray TransportAttribute::serialized() const { return QByteArray::number( d->mId ); } void TransportAttribute::deserialize( const QByteArray &data ) { d->mId = data.toInt(); } int TransportAttribute::transportId() const { return d->mId; } -Transport* TransportAttribute::transport() const +Transport *TransportAttribute::transport() const { return TransportManager::self()->transportById( d->mId, false ); } void TransportAttribute::setTransportId( int id ) { d->mId = id; } diff --git a/mailtransport/transportattribute.h b/mailtransport/transportattribute.h index 93f613120..2bf43cbd3 100644 --- a/mailtransport/transportattribute.h +++ b/mailtransport/transportattribute.h @@ -1,85 +1,85 @@ /* Copyright 2009 Constantin Berzan 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_TRANSPORTATTRIBUTE_H #define MAILTRANSPORT_TRANSPORTATTRIBUTE_H #include #include namespace MailTransport { class Transport; -/** +/** Attribute determining which transport to use for sending a message. @see mailtransport @see TransportManager. @author Constantin Berzan @since 4.4 */ class MAILTRANSPORT_EXPORT TransportAttribute : public Akonadi::Attribute { public: /** Creates a new TransportAttribute. */ TransportAttribute( int id = -1 ); /** Destroys this TransportAttribute. */ virtual ~TransportAttribute(); /* reimpl */ - virtual TransportAttribute* clone() const; + virtual TransportAttribute *clone() const; virtual QByteArray type() const; virtual QByteArray serialized() const; virtual void deserialize( const QByteArray &data ); /** Returns the transport id to use for sending this message. @see TransportManager. */ int transportId() const; /** Returns the transport object corresponding to the transport id contained in this attribute. @see Transport. */ - Transport* transport() const; + Transport *transport() const; /** Sets the transport id to use for sending this message. */ void setTransportId( int id ); private: class Private; Private *const d; }; } // namespace MailTransport #endif // MAILTRANSPORT_TRANSPORTATTRIBUTE_H diff --git a/mailtransport/transportmanager.cpp b/mailtransport/transportmanager.cpp index 7311d9311..f663ef64c 100644 --- a/mailtransport/transportmanager.cpp +++ b/mailtransport/transportmanager.cpp @@ -1,771 +1,771 @@ /* 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. */ #include "transportmanager.h" #include "addtransportdialog.h" #include "resourcesendjob.h" #include "mailtransport_defs.h" #include "sendmailconfigwidget.h" #include "sendmailjob.h" #include "smtpconfigwidget.h" #include "smtpjob.h" #include "transport.h" #include "transportconfigwidget.h" #include "transportjob.h" #include "transporttype.h" #include "transporttype_p.h" #include "transportconfigdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MailTransport; using namespace KWallet; /** * Private class that helps to provide binary compatibility between releases. * @internal */ class TransportManager::Private { public: Private() {} ~Private() { delete config; qDeleteAll( transports ); } KConfig *config; QList transports; TransportType::List types; bool myOwnChange; bool appliedChange; KWallet::Wallet *wallet; bool walletOpenFailed; bool walletAsyncOpen; int defaultTransportId; bool isMainInstance; QList walletQueue; }; class StaticTransportManager : public TransportManager { public: StaticTransportManager() : TransportManager() {} }; StaticTransportManager *sSelf = 0; static void destroyStaticTransportManager() { delete sSelf; } TransportManager::TransportManager() : QObject(), d( new Private ) { KGlobal::locale()->insertCatalog( QLatin1String( "libmailtransport" ) ); qAddPostRoutine( destroyStaticTransportManager ); d->myOwnChange = false; d->appliedChange = false; d->wallet = 0; d->walletOpenFailed = false; d->walletAsyncOpen = false; d->defaultTransportId = -1; d->config = new KConfig( QLatin1String( "mailtransports" ) ); QDBusConnection::sessionBus().registerObject( DBUS_OBJECT_PATH, this, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableSignals ); QDBusConnection::sessionBus().connect( QString(), QString(), DBUS_INTERFACE_NAME, DBUS_CHANGE_SIGNAL, this, SLOT(slotTransportsChanged()) ); d->isMainInstance = QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); connect( QDBusConnection::sessionBus().interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)), SLOT(dbusServiceOwnerChanged(QString,QString,QString)) ); fillTypes(); } TransportManager::~TransportManager() { qRemovePostRoutine( destroyStaticTransportManager ); delete d; } TransportManager *TransportManager::self() { if ( !sSelf ) { sSelf = new StaticTransportManager; sSelf->readConfig(); } return sSelf; } Transport *TransportManager::transportById( int id, bool def ) const { foreach ( Transport *t, d->transports ) { if ( t->id() == id ) { return t; } } if ( def || ( id == 0 && d->defaultTransportId != id ) ) { return transportById( d->defaultTransportId, false ); } return 0; } Transport *TransportManager::transportByName( const QString &name, bool def ) const { foreach ( Transport *t, d->transports ) { if ( t->name() == name ) { return t; } } if ( def ) { return transportById( 0, false ); } return 0; } QList< Transport * > TransportManager::transports() const { return d->transports; } TransportType::List TransportManager::types() const { return d->types; } Transport *TransportManager::createTransport() const { int id = createId(); Transport *t = new Transport( QString::number( id ) ); t->setId( id ); return t; } void TransportManager::addTransport( Transport *transport ) { if ( d->transports.contains( transport ) ) { kDebug() << "Already have this transport."; return; } kDebug() << "Added transport" << transport; d->transports.append( transport ); validateDefault(); emitChangesCommitted(); } void TransportManager::schedule( TransportJob *job ) { connect( job, SIGNAL(result(KJob*)), SLOT(jobResult(KJob*)) ); // check if the job is waiting for the wallet if ( !job->transport()->isComplete() ) { kDebug() << "job waits for wallet:" << job; d->walletQueue << job; loadPasswordsAsync(); return; } job->start(); } void TransportManager::createDefaultTransport() { KEMailSettings kes; Transport *t = createTransport(); t->setName( i18n( "Default Transport" ) ); t->setHost( kes.getSetting( KEMailSettings::OutServer ) ); if ( t->isValid() ) { t->writeConfig(); addTransport( t ); } else { kWarning() << "KEMailSettings does not contain a valid transport."; } } bool TransportManager::showNewTransportDialog( QWidget *parent ) { QPointer dialog = new AddTransportDialog( parent ); bool accepted = ( dialog->exec() == QDialog::Accepted ); delete dialog; return accepted; } bool TransportManager::promptCreateTransportIfNoneExists( QWidget *parent ) { if ( !isEmpty() ) { return true; } const int response = KMessageBox::messageBox( parent, KMessageBox::WarningContinueCancel, i18n( "You must create an outgoing account before sending." ), i18n( "Create Account Now?" ), KGuiItem( i18n( "Create Account Now" ) ) ); if ( response == KMessageBox::Continue ) { return showNewTransportDialog( parent ); } return false; } bool TransportManager::configureTransport( Transport *transport, QWidget *parent ) { if( transport->type() == Transport::EnumType::Akonadi ) { using namespace Akonadi; AgentInstance instance = AgentManager::self()->instance( transport->host() ); if( !instance.isValid() ) { kWarning() << "Invalid resource instance" << transport->host(); } instance.configure( parent ); // Async... transport->writeConfig(); return true; // No way to know here if the user cancelled or not. } QPointer dialog = new KDialog( parent ); TransportConfigWidget *configWidget = 0; switch( transport->type() ) { case Transport::EnumType::SMTP: { configWidget = new SMTPConfigWidget( transport, dialog ); break; } case Transport::EnumType::Sendmail: { configWidget = new SendmailConfigWidget( transport, dialog ); break; } default: { Q_ASSERT( false ); delete dialog; return false; } } dialog->setMainWidget( configWidget ); dialog->setCaption( i18n( "Configure account" ) ); dialog->setButtons( KDialog::Ok | KDialog::Cancel ); bool okClicked = ( dialog->exec() == QDialog::Accepted ); if( okClicked ) { configWidget->apply(); // calls transport->writeConfig() } delete dialog; return okClicked; } TransportJob *TransportManager::createTransportJob( int transportId ) { Transport *t = transportById( transportId, false ); if ( !t ) { return 0; } t = t->clone(); // Jobs delete their transports. t->updatePasswordState(); switch ( t->type() ) { case Transport::EnumType::SMTP: return new SmtpJob( t, this ); case Transport::EnumType::Sendmail: return new SendmailJob( t, this ); case Transport::EnumType::Akonadi: return new ResourceSendJob( t, this ); } Q_ASSERT( false ); return 0; } TransportJob *TransportManager::createTransportJob( const QString &transport ) { bool ok = false; Transport *t = 0; int transportId = transport.toInt( &ok ); if ( ok ) { t = transportById( transportId ); } if ( !t ) { t = transportByName( transport, false ); } if ( t ) { return createTransportJob( t->id() ); } return 0; } bool TransportManager::isEmpty() const { return d->transports.isEmpty(); } QList TransportManager::transportIds() const { QList rv; foreach ( Transport *t, d->transports ) { rv << t->id(); } return rv; } QStringList TransportManager::transportNames() const { QStringList rv; foreach ( Transport *t, d->transports ) { rv << t->name(); } return rv; } QString TransportManager::defaultTransportName() const { Transport *t = transportById( d->defaultTransportId, false ); if ( t ) { return t->name(); } return QString(); } int TransportManager::defaultTransportId() const { return d->defaultTransportId; } void TransportManager::setDefaultTransport( int id ) { if ( id == d->defaultTransportId || !transportById( id, false ) ) { return; } d->defaultTransportId = id; writeConfig(); } void TransportManager::removeTransport( int id ) { Transport *t = transportById( id, false ); if ( !t ) { return; } emit transportRemoved( t->id(), t->name() ); // Kill the resource, if Akonadi-type transport. if( t->type() == Transport::EnumType::Akonadi ) { using namespace Akonadi; const AgentInstance instance = AgentManager::self()->instance( t->host() ); if( !instance.isValid() ) { kWarning() << "Could not find resource instance."; } AgentManager::self()->removeInstance( instance ); } d->transports.removeAll( t ); validateDefault(); QString group = t->currentGroup(); delete t; d->config->deleteGroup( group ); writeConfig(); } void TransportManager::readConfig() { QList oldTransports = d->transports; d->transports.clear(); QRegExp re( QLatin1String( "^Transport (.+)$" ) ); QStringList groups = d->config->groupList().filter( re ); foreach ( const QString &s, groups ) { re.indexIn( s ); Transport *t = 0; // see if we happen to have that one already foreach ( Transport *old, oldTransports ) { if ( old->currentGroup() == QLatin1String( "Transport " ) + re.cap( 1 ) ) { kDebug() << "reloading existing transport:" << s; t = old; t->readConfig(); oldTransports.removeAll( old ); break; } } if ( !t ) { t = new Transport( re.cap( 1 ) ); } if ( t->id() <= 0 ) { t->setId( createId() ); t->writeConfig(); } d->transports.append( t ); } qDeleteAll( oldTransports ); oldTransports.clear(); // read default transport KConfigGroup group( d->config, "General" ); d->defaultTransportId = group.readEntry( "default-transport", 0 ); if ( d->defaultTransportId == 0 ) { // migrated default transport contains the name instead QString name = group.readEntry( "default-transport", QString() ); if ( !name.isEmpty() ) { Transport *t = transportByName( name, false ); if ( t ) { d->defaultTransportId = t->id(); writeConfig(); } } } validateDefault(); migrateToWallet(); } void TransportManager::writeConfig() { KConfigGroup group( d->config, "General" ); group.writeEntry( "default-transport", d->defaultTransportId ); d->config->sync(); emitChangesCommitted(); } void TransportManager::fillTypes() { Q_ASSERT( d->types.isEmpty() ); // SMTP. { TransportType type; type.d->mType = Transport::EnumType::SMTP; type.d->mName = i18nc( "@option SMTP transport", "SMTP" ); - type.d->mDescription = i18n( "An SMTP server on the internet" ); + type.d->mDescription = i18n( "An SMTP server on the Internet" ); d->types << type; } // Sendmail. { TransportType type; type.d->mType = Transport::EnumType::Sendmail; type.d->mName = i18nc( "@option sendmail transport", "Sendmail" ); type.d->mDescription = i18n( "A local sendmail installation" ); d->types << type; } // All Akonadi resources with MailTransport capability. { using namespace Akonadi; foreach ( const AgentType &atype, AgentManager::self()->types() ) { // TODO probably the string "MailTransport" should be #defined somewhere // and used like that in the resources (?) if( atype.capabilities().contains( QLatin1String( "MailTransport" ) ) ) { TransportType type; type.d->mType = Transport::EnumType::Akonadi; type.d->mAgentType = atype; type.d->mName = atype.name(); type.d->mDescription = atype.description(); d->types << type; kDebug() << "Found Akonadi type" << atype.name(); } } // Watch for appearing and disappearing types. connect( AgentManager::self(), SIGNAL(typeAdded(Akonadi::AgentType)), this, SLOT(agentTypeAdded(Akonadi::AgentType)) ); connect( AgentManager::self(), SIGNAL(typeRemoved(Akonadi::AgentType)), this, SLOT(agentTypeRemoved(Akonadi::AgentType)) ); } kDebug() << "Have SMTP, Sendmail, and" << d->types.count() - 2 << "Akonadi types."; } void TransportManager::emitChangesCommitted() { d->myOwnChange = true; // prevent us from reading our changes again d->appliedChange = false; // but we have to read them at least once emit transportsChanged(); emit changesCommitted(); } void TransportManager::slotTransportsChanged() { if ( d->myOwnChange && d->appliedChange ) { d->myOwnChange = false; d->appliedChange = false; return; } kDebug(); d->config->reparseConfiguration(); // FIXME: this deletes existing transport objects! readConfig(); d->appliedChange = true; // to prevent recursion emit transportsChanged(); } int TransportManager::createId() const { QList usedIds; foreach ( Transport *t, d->transports ) { usedIds << t->id(); } usedIds << 0; // 0 is default for unknown int newId; do { newId = KRandom::random(); } while ( usedIds.contains( newId ) ); return newId; } KWallet::Wallet * TransportManager::wallet() { if ( d->wallet && d->wallet->isOpen() ) { return d->wallet; } if ( !Wallet::isEnabled() || d->walletOpenFailed ) { return 0; } WId window = 0; if ( qApp->activeWindow() ) { window = qApp->activeWindow()->winId(); } else if ( !QApplication::topLevelWidgets().isEmpty() ) { window = qApp->topLevelWidgets().first()->winId(); } delete d->wallet; d->wallet = Wallet::openWallet( Wallet::NetworkWallet(), window ); if ( !d->wallet ) { d->walletOpenFailed = true; return 0; } prepareWallet(); return d->wallet; } void TransportManager::prepareWallet() { if ( !d->wallet ) { return; } if ( !d->wallet->hasFolder( WALLET_FOLDER ) ) { d->wallet->createFolder( WALLET_FOLDER ); } d->wallet->setFolder( WALLET_FOLDER ); } void TransportManager::loadPasswords() { foreach ( Transport *t, d->transports ) { t->readPassword(); } // flush the wallet queue foreach ( TransportJob *job, d->walletQueue ) { job->start(); } d->walletQueue.clear(); emit passwordsChanged(); } void TransportManager::loadPasswordsAsync() { kDebug(); // check if there is anything to do at all bool found = false; foreach ( Transport *t, d->transports ) { if ( !t->isComplete() ) { found = true; break; } } if ( !found ) { return; } // async wallet opening if ( !d->wallet && !d->walletOpenFailed ) { WId window = 0; if ( qApp->activeWindow() ) { window = qApp->activeWindow()->winId(); } else if ( !QApplication::topLevelWidgets().isEmpty() ) { window = qApp->topLevelWidgets().first()->winId(); } d->wallet = Wallet::openWallet( Wallet::NetworkWallet(), window, Wallet::Asynchronous ); if ( d->wallet ) { connect( d->wallet, SIGNAL(walletOpened(bool)), SLOT(slotWalletOpened(bool)) ); d->walletAsyncOpen = true; } else { d->walletOpenFailed = true; loadPasswords(); } return; } if ( d->wallet && !d->walletAsyncOpen ) { loadPasswords(); } } void TransportManager::slotWalletOpened( bool success ) { kDebug(); d->walletAsyncOpen = false; if ( !success ) { d->walletOpenFailed = true; delete d->wallet; d->wallet = 0; } else { prepareWallet(); } loadPasswords(); } void TransportManager::validateDefault() { if ( !transportById( d->defaultTransportId, false ) ) { if ( isEmpty() ) { d->defaultTransportId = -1; } else { d->defaultTransportId = d->transports.first()->id(); writeConfig(); } } } void TransportManager::migrateToWallet() { // check if we tried this already static bool firstRun = true; if ( !firstRun ) { return; } firstRun = false; // check if we are the main instance if ( !d->isMainInstance ) { return; } // check if migration is needed QStringList names; foreach ( Transport *t, d->transports ) { if ( t->needsWalletMigration() ) { names << t->name(); } } if ( names.isEmpty() ) { return; } // ask user if he wants to migrate int result = KMessageBox::questionYesNoList( 0, i18n( "The following mail transports store their passwords in an " "unencrypted configuration file.\nFor security reasons, " "please consider migrating these passwords to KWallet, the " "KDE Wallet management tool,\nwhich stores sensitive data " "for you in a strongly encrypted file.\n" "Do you want to migrate your passwords to KWallet?" ), names, i18n( "Question" ), KGuiItem( i18n( "Migrate" ) ), KGuiItem( i18n( "Keep" ) ), QString::fromAscii( "WalletMigrate" ) ); if ( result != KMessageBox::Yes ) { return; } // perform migration foreach ( Transport *t, d->transports ) { if ( t->needsWalletMigration() ) { t->migrateToWallet(); } } } void TransportManager::dbusServiceOwnerChanged( const QString &service, const QString &oldOwner, const QString &newOwner ) { Q_UNUSED( oldOwner ); if ( service == DBUS_SERVICE_NAME && newOwner.isEmpty() ) { QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); } } void TransportManager::agentTypeAdded( const Akonadi::AgentType &atype ) { using namespace Akonadi; if( atype.capabilities().contains( QLatin1String( "MailTransport" ) ) ) { TransportType type; type.d->mType = Transport::EnumType::Akonadi; type.d->mAgentType = atype; type.d->mName = atype.name(); type.d->mDescription = atype.description(); d->types << type; kDebug() << "Added new Akonadi type" << atype.name(); } } void TransportManager::agentTypeRemoved( const Akonadi::AgentType &atype ) { using namespace Akonadi; foreach ( const TransportType &type, d->types ) { if( type.type() == Transport::EnumType::Akonadi && type.agentType() == atype ) { d->types.removeAll( type ); kDebug() << "Removed Akonadi type" << atype.name(); } } } void TransportManager::jobResult( KJob *job ) { d->walletQueue.removeAll( static_cast( job ) ); } #include "transportmanager.moc"