diff --git a/kcal/incidenceformatter.cpp b/kcal/incidenceformatter.cpp index c7820571e..33fcf2488 100644 --- a/kcal/incidenceformatter.cpp +++ b/kcal/incidenceformatter.cpp @@ -1,1863 +1,1863 @@ /* This file is part of libkcal. Copyright (c) 2001 Cornelius Schumacher Copyright (c) 2004 Reinhold Kainhofer Copyright (c) 2005 Rafal Rzepecki 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 "incidenceformatter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include #include #include #include #include #include #include //Added by qt3to4: #include #include using namespace KCal; /******************************************************************* * Helper functions for the extensive display (event viewer) *******************************************************************/ 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.find( "\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( "h2", i18n("Category") ); } else { tmpStr = eventViewerAddTag( "h2", 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.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 = QString::null; } kdDebug(5850) << "formatAttendees: uid = " << uid << endl; // 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() ) { KCal::Person person( name, email ); KURL mailto; mailto.setProtocol( "mailto" ); mailto.setPath( person.fullName() ); 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; const QString iconPath = iconLoader.iconPath( "mail_generic", KIcon::Small ); // Add organizer link tmpStr += eventViewerAddTag( "h3", i18n("Organizer") ); tmpStr += "
      "; tmpStr += linkPerson( event->organizer().email(), event->organizer().name(), QString::null, iconPath ); tmpStr += "
    "; // Add attendees links tmpStr += eventViewerAddTag( "h3", i18n("Attendees") ); tmpStr += "
      "; Attendee::List::ConstIterator it; for( it = attendees.begin(); it != attendees.end(); ++it ) { Attendee *a = *it; tmpStr += linkPerson( a->email(), a->name(), a->uid(), iconPath ); } 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.begin(); it != as.end(); ++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::null; if ( event->customProperty("KABC","BIRTHDAY")!= "YES" ) return QString::null; 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; const QString iconPath = iconLoader.iconPath( "mail_generic", KIcon::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; tmpStr += ""; } tmpStr += ""; tmpStr += "
    "; if ( incidence->type() == "Todo" ) { tmpStr += ""; } if ( incidence->isAlarmEnabled() ) { tmpStr += ""; } if ( incidence->doesRecur() ) { tmpStr += ""; } if ( incidence->isReadOnly() ) { tmpStr += ""; } tmpStr += "" + eventViewerAddTag( "h1", incidence->summary() ) + "

    "; return tmpStr; } static QString eventViewerFormatEvent( Event *event ) { if ( !event ) return QString::null; QString tmpStr = eventViewerFormatHeader( event ); tmpStr += ""; if ( !event->location().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += ""; tmpStr += ""; } tmpStr += ""; if ( event->doesFloat() ) { 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->location() + "
    " + i18n( "Time" ) + "" + i18n(" - ","%1 - %2") .arg( event->dtStartDateStr() ) .arg( event->dtEndDateStr() ) + "" + i18n( "Date" ) + "" + i18n("date as string","%1").arg( event->dtStartDateStr() ) + "" + i18n( "Time" ) + "" + i18n(" - ","%1 - %2") .arg( event->dtStartStr() ) .arg( event->dtEndStr() ) + "" + i18n( "Time" ) + "" + i18n(" - ","%1 - %2") .arg( event->dtStartTimeStr() ) .arg( event->dtEndTimeStr() ) + "" + event->dtStartTimeStr() + "
    " + i18n( "Date" ) + "" + i18n("date as string","%1") .arg( event->dtStartDateStr() ) + "
    " + i18n( "Birthday" ) + "" + eventViewerFormatBirthday( event ) + "
    "; return tmpStr; } if ( !event->description().isEmpty() ) { tmpStr += ""; tmpStr += ""; tmpStr += "" + eventViewerAddTag( "p", event->description() ) + ""; tmpStr += ""; } if ( event->categories().count() > 0 ) { tmpStr += ""; tmpStr += "" + i18n( "1 category", "%n categories", event->categories().count() )+ ""; tmpStr += "" + event->categoriesStr() + ""; tmpStr += ""; } if ( event->doesRecur() ) { QDateTime dt = event->recurrence()->getNextDateTime( QDateTime::currentDateTime() ); tmpStr += ""; tmpStr += "" + i18n( "Next Occurrence" )+ ""; tmpStr += "" + KGlobal::locale()->formatDateTime( dt, true ) + ""; tmpStr += ""; } tmpStr += ""; tmpStr += eventViewerFormatAttendees( event ); tmpStr += ""; int attachmentCount = event->attachments().count(); if ( attachmentCount > 0 ) { tmpStr += ""; tmpStr += "" + i18n( "1 attachment", "%n attachments", attachmentCount )+ ""; tmpStr += "" + eventViewerFormatAttachments( event ) + ""; tmpStr += ""; } tmpStr += ""; tmpStr += "

    " + i18n( "Creation date: %1.").arg( KGlobal::locale()->formatDateTime( event->created() , true ) ) + ""; return tmpStr; } static QString eventViewerFormatTodo( Todo *todo ) { if ( !todo ) return QString::null; QString tmpStr = eventViewerFormatHeader( todo ); if ( !todo->location().isEmpty() ) { tmpStr += eventViewerAddTag( "b", i18n("Location:") ); tmpStr += todo->location(); tmpStr += "
    "; } if ( todo->hasDueDate() ) { tmpStr += i18n("Due on: %1").arg( todo->dtDueStr() ); } if ( !todo->description().isEmpty() ) tmpStr += eventViewerAddTag( "p", todo->description() ); tmpStr += eventViewerFormatCategories( todo ); tmpStr += i18n("

    Priority: %2

    ") .arg( (todo->priority()>0)?(QString::number( todo->priority() )):i18n("Unspecified") ); tmpStr += i18n("

    %1 % completed

    ") .arg( todo->percentComplete() ); if ( todo->doesRecur() ) { QDateTime dt = todo->recurrence()->getNextDateTime( QDateTime::currentDateTime() ); tmpStr += eventViewerAddTag( "p", "" + i18n("This is a recurring to-do. The next occurrence will be on %1.").arg( KGlobal::locale()->formatDateTime( dt, true ) ) + "" ); } tmpStr += eventViewerFormatAttendees( todo ); tmpStr += eventViewerFormatAttachments( todo ); tmpStr += "

    " + i18n( "Creation date: %1.").arg( KGlobal::locale()->formatDateTime( todo->created() , true ) ) + ""; return tmpStr; } static QString eventViewerFormatJournal( Journal *journal ) { if ( !journal ) return QString::null; QString tmpStr; if ( !journal->summary().isEmpty() ) tmpStr+= eventViewerAddTag( "h1", journal->summary() ); tmpStr += eventViewerAddTag( "h2", i18n("Journal for %1").arg( journal->dtStartDateStr( false ) ) ); if ( !journal->description().isEmpty() ) tmpStr += eventViewerAddTag( "p", journal->description() ); return tmpStr; } static QString eventViewerFormatFreeBusy( FreeBusy *fb ) { if ( !fb ) return QString::null; QString tmpStr( eventViewerAddTag( "h1", i18n("Free/Busy information for %1").arg( fb->organizer().fullName() ) ) ); tmpStr += eventViewerAddTag( "h3", i18n("Busy times in date range %1 - %2:") .arg( KGlobal::locale()->formatDate( fb->dtStart().date(), true ) ) .arg( KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ) ); QList periods = fb->busyPeriods(); QString text = eventViewerAddTag( "em", eventViewerAddTag( "b", i18n("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 += i18n("1 hour ", "%n hours ", dur / 3600 ); dur %= 3600; } if ( dur >= 60 ) { cont += i18n("1 minute ", "%n minutes ", dur / 60); dur %= 60; } if ( dur > 0 ) { cont += i18n("1 second", "%n seconds", dur); } text += i18n("startDate for duration", "%1 for %2") .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) .arg( cont ); text += "
    "; } else { if ( per.start().date() == per.end().date() ) { text += i18n("date, fromTime - toTime ", "%1, %2 - %3") .arg( KGlobal::locale()->formatDate( per.start().date() ) ) .arg( KGlobal::locale()->formatTime( per.start().time() ) ) .arg( KGlobal::locale()->formatTime( per.end().time() ) ); } else { text += i18n("fromDateTime - toDateTime", "%1 - %2") .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) .arg( KGlobal::locale()->formatDateTime( per.end(), false ) ); } text += "
    "; } } tmpStr += eventViewerAddTag( "p", text ); return tmpStr; } class IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor { public: EventViewerVisitor() { mResult = ""; } bool act( IncidenceBase *incidence ) { return incidence->accept( *this ); } QString result() const { return mResult; } protected: bool visit( Event *event ) { mResult = eventViewerFormatEvent( event ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = eventViewerFormatTodo( todo ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = eventViewerFormatJournal( journal ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = eventViewerFormatFreeBusy( fb ); return !mResult.isEmpty(); } protected: QString mResult; }; QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence ) { if ( !incidence ) return QString::null; EventViewerVisitor v; if ( v.act( incidence ) ) { return v.result(); } else return QString::null; } /******************************************************************* * Helper functions for the body part formatter of kmail *******************************************************************/ static QString string2HTML( const QString& str ) { return Q3StyleSheet::convertFromPlainText(str); } static QString invitationRow( const QString &cell1, const QString &cell2 ) { return "" + cell1 + "" + cell2 + "\n"; } static QString invitationDetailsEvent( Event* event ) { // Meeting details are formatted into an HTML table if ( !event ) return QString::null; QString html; QString tmp; QString sSummary = i18n( "Summary unspecified" ); if ( ! event->summary().isEmpty() ) { sSummary = string2HTML( event->summary() ); } QString sLocation = i18n( "Location unspecified" ); if ( ! event->location().isEmpty() ) { sLocation = string2HTML( event->location() ); } QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" ); html = QString("

    \n").arg(dir); html += "\n"; // Meeting summary & location rows html += invitationRow( i18n( "What:" ), sSummary ); html += invitationRow( i18n( "Where:" ), sLocation ); // Meeting Start Time Row if ( ! event->doesFloat() ) { tmp = i18n("%1: Start Date, %2: Start Time", "%1 %2") .arg( event->dtStartDateStr(), event->dtStartTimeStr() ); } else { tmp = i18n("%1: Start Date", "%1 (time unspecified)") .arg( event->dtStartDateStr() ); } html += invitationRow( i18n( "Start Time:" ), tmp ); // Meeting End Time Row if ( event->hasEndDate() ) { if ( ! event->doesFloat() ) { tmp = i18n("%1: End Date, %2: End Time", "%1 %2") .arg( event->dtEndDateStr(), event->dtEndTimeStr() ); } else { tmp = i18n("%1: End Date", "%1 (time unspecified)") .arg( event->dtEndDateStr() ); } } else { tmp = i18n( "Unspecified" ); } html += invitationRow( i18n( "End Time:" ), tmp ); // Meeting Duration Row if ( !event->doesFloat() && event->hasEndDate() ) { tmp = QString::null; QTime sDuration(0,0,0), t; int secs = event->dtStart().secsTo( event->dtEnd() ); t = sDuration.addSecs( secs ); if ( t.hour() > 0 ) { tmp += i18n( "1 hour ", "%n hours ", t.hour() ); } if ( t.minute() > 0 ) { tmp += i18n( "1 minute ", "%n minutes ", t.minute() ); } html += invitationRow( i18n( "Duration:" ), tmp ); } html += "
    \n"; html += "
    \n"; return html; } static QString invitationDetailsTodo( Todo *todo ) { // Task details are formatted into an HTML table if ( !todo ) return QString::null; QString sSummary = i18n( "Summary unspecified" ); QString sDescr = i18n( "Description unspecified" ); if ( ! todo->summary().isEmpty() ) { sSummary = todo->summary(); } if ( ! todo->description().isEmpty() ) { sDescr = todo->description(); } QString html( "\n" ); html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
    \n"; return html; } static QString invitationDetailsJournal( Journal *journal ) { if ( !journal ) return QString::null; QString sSummary = i18n( "Summary unspecified" ); QString sDescr = i18n( "Description unspecified" ); if ( ! journal->summary().isEmpty() ) { sSummary = journal->summary(); } if ( ! journal->description().isEmpty() ) { sDescr = journal->description(); } QString html( "\n" ); html += invitationRow( i18n( "Summary:" ), sSummary ); html += invitationRow( i18n( "Date:" ), journal->dtStartDateStr( false ) ); html += invitationRow( i18n( "Description:" ), sDescr ); html += "
    \n"; return html; } static QString invitationDetailsFreeBusy( FreeBusy *fb ) { if ( !fb ) return QString::null; QString html( "\n" ); html += invitationRow( i18n("Person:"), fb->organizer().fullName() ); html += invitationRow( i18n("Start date:"), fb->dtStartDateStr() ); html += invitationRow( i18n("End date:"), KGlobal::locale()->formatDate( fb->dtEnd().date(), true ) ); 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 += i18n("1 hour ", "%n hours ", dur / 3600); dur %= 3600; } if ( dur >= 60 ) { cont += i18n("1 minute", "%n minutes ", dur / 60); dur %= 60; } if ( dur > 0 ) { cont += i18n("1 second", "%n seconds", dur); } html += invitationRow( QString::null, i18n("startDate for duration", "%1 for %2") .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) .arg(cont) ); } else { QString cont; if ( per.start().date() == per.end().date() ) { cont = i18n("date, fromTime - toTime ", "%1, %2 - %3") .arg( KGlobal::locale()->formatDate( per.start().date() ) ) .arg( KGlobal::locale()->formatTime( per.start().time() ) ) .arg( KGlobal::locale()->formatTime( per.end().time() ) ); } else { cont = i18n("fromDateTime - toDateTime", "%1 - %2") .arg( KGlobal::locale()->formatDateTime( per.start(), false ) ) .arg( KGlobal::locale()->formatDateTime( per.end(), false ) ); } html += invitationRow( QString::null, 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::null; switch ( msg->method() ) { case Scheduler::Publish: return i18n("This event has been published"); case Scheduler::Request: return i18n( "You have been invited to this meeting" ); case Scheduler::Refresh: return i18n( "This invitation was refreshed" ); case Scheduler::Cancel: return i18n( "This meeting has been canceled" ); case Scheduler::Add: return i18n( "Addition to the meeting invitation" ); case Scheduler::Reply: { Attendee::List attendees = event->attendees(); if( attendees.count() == 0 ) { kdDebug(5850) << "No attendees in the iCal reply!\n"; return QString::null; } if( attendees.count() != 1 ) kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " << "but is " << attendees.count() << endl; Attendee* attendee = *attendees.begin(); switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "Sender indicates this invitation still needs some action" ); case Attendee::Accepted: return i18n( "Sender accepts this meeting invitation" ); case Attendee::Tentative: return i18n( "Sender tentatively accepts this meeting invitation" ); case Attendee::Declined: return i18n( "Sender declines this meeting invitation" ); case Attendee::Delegated: return i18n( "Sender has delegated this meeting invitation" ); case Attendee::Completed: return i18n( "This meeting invitation is now completed" ); case Attendee::InProcess: return i18n( "Sender is still processing the invitation" ); default: return i18n( "Unknown response to this meeting invitation" ); } break; } case Scheduler::Counter: return i18n( "Sender makes this counter proposal" ); case Scheduler::Declinecounter: return i18n( "Sender declines the counter proposal" ); case Scheduler::NoMethod: return i18n("Error: iMIP message with unknown method: '%1'") .arg( msg->method() ); } return QString::null; } static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg ) { if ( !msg || !todo ) return QString::null; switch ( msg->method() ) { case Scheduler::Publish: return i18n("This task has been published"); case Scheduler::Request: return i18n( "You have been assigned this task" ); case Scheduler::Refresh: return i18n( "This task was refreshed" ); case Scheduler::Cancel: return i18n( "This task was canceled" ); case Scheduler::Add: return i18n( "Addition to the task" ); case Scheduler::Reply: { Attendee::List attendees = todo->attendees(); if( attendees.count() == 0 ) { kdDebug(5850) << "No attendees in the iCal reply!\n"; return QString::null; } if( attendees.count() != 1 ) kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " << "but is " << attendees.count() << endl; Attendee* attendee = *attendees.begin(); switch( attendee->status() ) { case Attendee::NeedsAction: return i18n( "Sender indicates this task assignment still needs some action" ); case Attendee::Accepted: return i18n( "Sender accepts this task" ); case Attendee::Tentative: return i18n( "Sender tentatively accepts this task" ); case Attendee::Declined: return i18n( "Sender declines this task" ); case Attendee::Delegated: return i18n( "Sender has delegated this request for the task " ); case Attendee::Completed: return i18n( "The request for this task is now completed" ); case Attendee::InProcess: return i18n( "Sender is still processing the invitation" ); default: return i18n( "Unknown response to this task" ); } break; } case Scheduler::Counter: return i18n( "Sender makes this counter proposal" ); case Scheduler::Declinecounter: return i18n( "Sender declines the counter proposal" ); case Scheduler::NoMethod: return i18n("Error: iMIP message with unknown method: '%1'") .arg( msg->method() ); } return QString::null; } 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::null; switch ( msg->method() ) { case Scheduler::Publish: return i18n("This journal has been published"); case Scheduler::Request: return i18n( "You have been assigned this journal" ); case Scheduler::Refresh: return i18n( "This journal was refreshed" ); case Scheduler::Cancel: return i18n( "This journal was canceled" ); case Scheduler::Add: return i18n( "Addition to the journal" ); case Scheduler::Reply: { Attendee::List attendees = journal->attendees(); if( attendees.count() == 0 ) { kdDebug(5850) << "No attendees in the iCal reply!\n"; return QString::null; } if( attendees.count() != 1 ) kdDebug(5850) << "Warning: attendeecount in the reply should be 1 " << "but is " << attendees.count() << endl; 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 Scheduler::Counter: return i18n( "Sender makes this counter proposal" ); case Scheduler::Declinecounter: return i18n( "Sender declines the counter proposal" ); case Scheduler::NoMethod: return i18n("Error: iMIP message with unknown method: '%1'") .arg( msg->method() ); } return QString::null; } static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg ) { if ( !msg || !fb ) return QString::null; switch ( msg->method() ) { case Scheduler::Publish: return i18n("This free/busy list has been published"); case Scheduler::Request: return i18n( "The free/busy list has been requested" ); case Scheduler::Refresh: return i18n( "This free/busy list was refreshed" ); case Scheduler::Cancel: return i18n( "This free/busy list was canceled" ); case Scheduler::Add: return i18n( "Addition to the free/busy list" ); case Scheduler::NoMethod: default: return i18n("Error: Free/Busy iMIP message with unknown method: '%1'") .arg( msg->method() ); } } class 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 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 IncidenceFormatter::InvitationBodyVisitor : public IncidenceFormatter::ScheduleMessageVisitor { protected: bool visit( Event *event ) { mResult = invitationDetailsEvent( event ); return !mResult.isEmpty(); } bool visit( Todo *todo ) { mResult = invitationDetailsTodo( todo ); return !mResult.isEmpty(); } bool visit( Journal *journal ) { mResult = invitationDetailsJournal( journal ); return !mResult.isEmpty(); } bool visit( FreeBusy *fb ) { mResult = invitationDetailsFreeBusy( fb ); return !mResult.isEmpty(); } }; QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text ) { QString res( "%2" ); return res.arg( generateLinkURL( id ) ).arg( text ); return res; } QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar, InvitationFormatterHelper *helper ) { if ( invitation.isEmpty() ) return QString::null; 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 ) { kdDebug( 5850 ) << "Failed to parse the scheduling message" << endl; Q_ASSERT( format.exception() ); kdDebug( 5850 ) << format.exception()->message() << endl; return QString::null; } IncidenceBase *incBase = msg->event(); // First make the text of the message QString html; QString tableStyle = QString::fromLatin1( "style=\"border: solid 1px; margin: 0em;\"" ); QString tableHead = QString::fromLatin1( "
    " "" "
    ").arg(tableStyle); html += tableHead; InvitationHeaderVisitor headerVisitor; // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled if ( !headerVisitor.act( incBase, msg ) ) return QString::null; html += "

    " + headerVisitor.result() + "

    "; InvitationBodyVisitor bodyVisitor; if ( !bodyVisitor.act( incBase, msg ) ) return QString::null; html += bodyVisitor.result(); html += "
     
     
    "; html += "
     "; #if 0 html += helper->makeLinkURL( "accept", i18n("[Enter this into my calendar]") ); html += "   "; #endif // Add groupware links switch ( msg->method() ) { case Scheduler::Publish: case Scheduler::Request: case Scheduler::Refresh: case Scheduler::Add: // Accept html += helper->makeLink( "accept", i18n( "[Accept]" ) ); html += "   "; html += helper->makeLink( "accept_conditionally", i18n( "Accept conditionally", "[Accept cond.]" ) ); html += "   "; // Decline html += helper->makeLink( "decline", i18n( "[Decline]" ) ); #if 0 // TODO: implement this html += "   "; html += helper->makeLink( "check_calendar", i18n("[Check my calendar...]" ) ); #endif break; case Scheduler::Cancel: // Cancel event from my calendar html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) ); break; case Scheduler::Reply: // Enter this into my calendar if ( incBase->type() == "Todo" ) { html += helper->makeLink( "reply", i18n( "[Enter this into my task list]" ) ); } else { html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) ); } break; case Scheduler::Counter: case Scheduler::Declinecounter: case Scheduler::NoMethod: break; } html += "
    "; Incidence* incidence = dynamic_cast( incBase ); if ( incidence ) { QString sDescr = incidence->description(); if( ( msg->method() == Scheduler::Request || msg->method() == Scheduler::Cancel ) && !sDescr.isEmpty() ) { html += "
     
     
    " + i18n("Description:") + "
     "; html += string2HTML(sDescr) + "
    "; } } html += "

    "; return html; } /******************************************************************* * Helper functions for the msTNEF -> VPart converter *******************************************************************/ //----------------------------------------------------------------------------- -static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key, +static QString stringProp( KTNEFMessage* tnefMsg, const quint32& key, const QString& fallback = QString::null) { return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16, fallback ); } static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name, const QString& fallback = QString::null ) { return tnefMsg->findNamedProp( name, fallback ); } struct save_tz { char* old_tz; char* tz_env_str; }; /* temporarily go to a different timezone */ static struct save_tz set_tz( const char* _tc ) { const char *tc = _tc?_tc:"UTC"; struct save_tz rv; rv.old_tz = 0; rv.tz_env_str = 0; //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl; char* tz_env = 0; if( getenv( "TZ" ) ) { tz_env = strdup( getenv( "TZ" ) ); rv.old_tz = tz_env; } char* tmp_env = (char*)malloc( strlen( tc ) + 4 ); strcpy( tmp_env, "TZ=" ); strcpy( tmp_env+3, tc ); putenv( tmp_env ); rv.tz_env_str = tmp_env; /* tmp_env is not free'ed -- it is part of the environment */ tzset(); //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl; return rv; } /* restore previous timezone */ static void unset_tz( struct save_tz old_tz ) { if( old_tz.old_tz ) { char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 ); strcpy( tmp_env, "TZ=" ); strcpy( tmp_env+3, old_tz.old_tz ); putenv( tmp_env ); /* tmp_env is not free'ed -- it is part of the environment */ free( old_tz.old_tz ); } else { /* clear TZ from env */ putenv( strdup("TZ") ); } tzset(); /* is this OK? */ if( old_tz.tz_env_str ) free( old_tz.tz_env_str ); } static QDateTime utc2Local( const QDateTime& utcdt ) { struct tm tmL; save_tz tmp_tz = set_tz("UTC"); time_t utc = utcdt.toTime_t(); unset_tz( tmp_tz ); localtime_r( &utc, &tmL ); return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ), QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) ); } static QDateTime pureISOToLocalQDateTime( const QString& dtStr, bool bDateOnly = false ) { QDate tmpDate; QTime tmpTime; int year, month, day, hour, minute, second; if( bDateOnly ) { year = dtStr.left( 4 ).toInt(); month = dtStr.mid( 4, 2 ).toInt(); day = dtStr.mid( 6, 2 ).toInt(); hour = 0; minute = 0; second = 0; } else { year = dtStr.left( 4 ).toInt(); month = dtStr.mid( 4, 2 ).toInt(); day = dtStr.mid( 6, 2 ).toInt(); hour = dtStr.mid( 9, 2 ).toInt(); minute = dtStr.mid( 11, 2 ).toInt(); second = dtStr.mid( 13, 2 ).toInt(); } tmpDate.setYMD( year, month, day ); tmpTime.setHMS( hour, minute, second ); if( tmpDate.isValid() && tmpTime.isValid() ) { QDateTime dT = QDateTime( tmpDate, tmpTime ); if( !bDateOnly ) { // correct for GMT ( == Zulu time == UTC ) if (dtStr.at(dtStr.length()-1) == 'Z') { //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() ); //localUTCOffset( dT ) ); dT = utc2Local( dT ); } } return dT; } else return QDateTime(); } QString IncidenceFormatter::msTNEFToVPart( const QByteArray& tnef ) { bool bOk = false; KTNEFParser parser; QByteArray b( tnef ); QBuffer buf( &b ); CalendarLocal cal ( QLatin1String( "UTC" ) ); KABC::Addressee addressee; KABC::VCardConverter cardConv; ICalFormat calFormat; Event* event = new Event(); if( parser.openDevice( &buf ) ) { KTNEFMessage* tnefMsg = parser.message(); //QMap props = parser.message()->properties(); // Everything depends from property PR_MESSAGE_CLASS // (this is added by KTNEFParser): QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true ) .upper(); if( !msgClass.isEmpty() ) { // Match the old class names that might be used by Outlook for // compatibility with Microsoft Mail for Windows for Workgroups 3.1. bool bCompatClassAppointment = false; bool bCompatMethodRequest = false; bool bCompatMethodCancled = false; bool bCompatMethodAccepted = false; bool bCompatMethodAcceptedCond = false; bool bCompatMethodDeclined = false; if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) { bCompatClassAppointment = true; if( msgClass.endsWith( ".MTGREQ" ) ) bCompatMethodRequest = true; if( msgClass.endsWith( ".MTGCNCL" ) ) bCompatMethodCancled = true; if( msgClass.endsWith( ".MTGRESPP" ) ) bCompatMethodAccepted = true; if( msgClass.endsWith( ".MTGRESPA" ) ) bCompatMethodAcceptedCond = true; if( msgClass.endsWith( ".MTGRESPN" ) ) bCompatMethodDeclined = true; } bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" ); if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) { // Compose a vCal bool bIsReply = false; QString prodID = "-//Microsoft Corporation//Outlook "; prodID += tnefMsg->findNamedProp( "0x8554", "9.0" ); prodID += "MIMEDIR/EN\n"; prodID += "VERSION:2.0\n"; calFormat.setApplication( "Outlook", prodID ); Scheduler::Method method; if( bCompatMethodRequest ) method = Scheduler::Request; else if( bCompatMethodCancled ) method = Scheduler::Cancel; else if( bCompatMethodAccepted || bCompatMethodAcceptedCond || bCompatMethodDeclined ) { method = Scheduler::Reply; bIsReply = true; } else { // pending(khz): verify whether "0x0c17" is the right tag ??? // // at the moment we think there are REQUESTS and UPDATES // // but WHAT ABOUT REPLIES ??? // // if( tnefMsg->findProp(0x0c17) == "1" ) bIsReply = true; method = Scheduler::Request; } /// ### FIXME Need to get this attribute written ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown ); QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) ); if( !sSenderSearchKeyEmail.isEmpty() ) { int colon = sSenderSearchKeyEmail.find( ':' ); // May be e.g. "SMTP:KHZ@KDE.ORG" if( sSenderSearchKeyEmail.find( ':' ) == -1 ) sSenderSearchKeyEmail.remove( 0, colon+1 ); } QString s( tnefMsg->findProp( 0x0e04 ) ); QStringList attendees = QStringList::split( ';', s ); if( attendees.count() ) { for( QStringList::Iterator it = attendees.begin(); it != attendees.end(); ++it ) { // Skip all entries that have no '@' since these are // no mail addresses if( (*it).find('@') == -1 ) { s = (*it).trimmed(); Attendee *attendee = new Attendee( s, s, true ); if( bIsReply ) { if( bCompatMethodAccepted ) attendee->setStatus( Attendee::Accepted ); if( bCompatMethodDeclined ) attendee->setStatus( Attendee::Declined ); if( bCompatMethodAcceptedCond ) attendee->setStatus(Attendee::Tentative); } else { attendee->setStatus( Attendee::NeedsAction ); attendee->setRole( Attendee::ReqParticipant ); } event->addAttendee(attendee); } } } else { // Oops, no attendees? // This must be old style, let us use the PR_SENDER_SEARCH_KEY. s = sSenderSearchKeyEmail; if( !s.isEmpty() ) { Attendee *attendee = new Attendee( QString::null, QString::null, true ); if( bIsReply ) { if( bCompatMethodAccepted ) attendee->setStatus( Attendee::Accepted ); if( bCompatMethodAcceptedCond ) attendee->setStatus( Attendee::Declined ); if( bCompatMethodDeclined ) attendee->setStatus( Attendee::Tentative ); } else { attendee->setStatus(Attendee::NeedsAction); attendee->setRole(Attendee::ReqParticipant); } event->addAttendee(attendee); } } s = tnefMsg->findProp( 0x0c1f ); // look for organizer property if( s.isEmpty() && !bIsReply ) s = sSenderSearchKeyEmail; // TODO: Use the common name? if( !s.isEmpty() ) event->setOrganizer( s ); s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null ) .replace( QChar( ':' ), QString::null ); event->setDtStart( QDateTime::fromString( s ) ); // ## Format?? s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null ) .replace( QChar( ':' ), QString::null ); event->setDtEnd( QDateTime::fromString( s ) ); s = tnefMsg->findProp( 0x8208 ); event->setLocation( s ); // is it OK to set this to OPAQUE always ?? //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme! //vPart += "SEQUENCE:0\n"; // is "0x0023" OK - or should we look for "0x0003" ?? s = tnefMsg->findProp( 0x0023 ); event->setUid( s ); // PENDING(khz): is this value in local timezone? Must it be // adjusted? Most likely this is a bug in the server or in // Outlook - we ignore it for now. s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null ) .replace( QChar( ':' ), QString::null ); // ### libkcal always uses currentDateTime() // event->setDtStamp(QDateTime::fromString(s)); s = tnefMsg->findNamedProp( "Keywords" ); event->setCategories( s ); s = tnefMsg->findProp( 0x1000 ); event->setDescription( s ); s = tnefMsg->findProp( 0x0070 ); event->setSummary( s ); s = tnefMsg->findProp( 0x0026 ); event->setPriority( s.toInt() ); // is reminder flag set ? if(!tnefMsg->findProp(0x8503).isEmpty()) { Alarm *alarm = new Alarm(event); QDateTime highNoonTime = pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 ) .replace( QChar( '-' ), "" ) .replace( QChar( ':' ), "" ) ); QDateTime wakeMeUpTime = pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" ) .replace( QChar( '-' ), "" ) .replace( QChar( ':' ), "" ) ); alarm->setTime(wakeMeUpTime); if( highNoonTime.isValid() && wakeMeUpTime.isValid() ) alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) ); else // default: wake them up 15 minutes before the appointment alarm->setStartOffset( Duration( 15*60 ) ); alarm->setDisplayAlarm( i18n( "Reminder" ) ); // Sorry: the different action types are not known (yet) // so we always set 'DISPLAY' (no sounds, no images...) event->addAlarm( alarm ); } cal.addEvent( event ); bOk = true; // we finished composing a vCal } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) { addressee.setUid( stringProp( tnefMsg, attMSGID ) ); addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) ); addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true ); addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false ); addressee.insertEmail( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false ); addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, MAPI_TAG_CONTACT_IMADDRESS ) ); addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, MAPI_TAG_PR_SPOUSE_NAME ) ); addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) ); addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( tnefMsg, MAPI_TAG_PR_ASSISTANT ) ); addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME ) ); addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION ) ); addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, MAPI_TAG_PR_PROFESSION ) ); QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY ) .replace( QChar( '-' ), QString::null ) .replace( QChar( ':' ), QString::null ); if( !s.isEmpty() ) addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s ); addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE ) ) ); // collect parts of Name entry addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) ); addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) ); addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) ); addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) ); addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) ); addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) ); addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) ); addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) ); /* the MAPI property ID of this (multiline) )field is unknown: vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" ); */ KABC::Address adr; adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) ); adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) ); adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) ); adr.setRegion( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) ); adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE ) ); adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) ); adr.setType(KABC::Address::Home); addressee.insertAddress(adr); adr.setPostOfficeBox( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) ); adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) ); adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) ); adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) ); adr.setPostalCode( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) ); adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY ) ); adr.setType( KABC::Address::Work ); addressee.insertAddress( adr ); adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) ); adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) ); adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) ); adr.setRegion( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) ); adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE ) ); adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) ); adr.setType( KABC::Address::Dom ); addressee.insertAddress(adr); // problem: the 'other' address was stored by KOrganizer in // a line looking like the following one: // vPart += "\nADR;TYPE=dom;TYPE=intl;TYPE=parcel;TYPE=postal;TYPE=work;TYPE=home:other_pobox;;other_str1\nother_str2;other_loc;other_region;other_pocode;other_country QString nr; nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Home ) ); nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER ); addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | KABC::PhoneNumber::Work ) ); s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY ) .replace( QChar( '-' ), QString::null ) .replace( QChar( ':' ), QString::null ); if( !s.isEmpty() ) addressee.setBirthday( QDateTime::fromString( s ) ); bOk = ( !addressee.isEmpty() ); } else if( "IPM.NOTE" == msgClass ) { } // else if ... and so on ... } } // Compose return string QString iCal = calFormat.toString( &cal ); if( !iCal.isEmpty() ) // This was an iCal return iCal; // Not an iCal - try a vCard KABC::VCardConverter converter; return converter.createVCard( addressee ); } QString IncidenceFormatter::formatTNEFInvitation( const QByteArray& tnef, Calendar *mCalendar, InvitationFormatterHelper *helper ) { QString vPart = IncidenceFormatter::msTNEFToVPart( tnef ); QString iCal = IncidenceFormatter::formatICalInvitation( vPart, mCalendar, helper ); if( !iCal.isEmpty() ) return iCal; return vPart; } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ class IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor { public: ToolTipVisitor() : mRichText( true ), mResult( "" ) {} bool act( IncidenceBase *incidence, bool richText=true) { mRichText = richText; 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; QString mResult; }; QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event*event ) { QString ret; QString tmp; if ( event->isMultiDay() ) { tmp = "
    " + i18n("Event start", "From: %1"); if (event->doesFloat()) ret += tmp.arg( event->dtStartDateStr().replace(" ", " ") ); else ret += tmp.arg( event->dtStartStr().replace(" ", " ") ); tmp = "
    " + i18n("Event end","To: %1"); if (event->doesFloat()) ret += tmp.arg( event->dtEndDateStr().replace(" ", " ") ); else ret += tmp.arg( event->dtEndStr().replace(" ", " ") ); } else { ret += "
    "+i18n("Date: %1"). arg( event->dtStartDateStr().replace(" ", " ") ); if ( !event->doesFloat() ) { if ( event->dtStartTimeStr() == event->dtEndTimeStr() ) { // to prevent 'Time: 17:00 - 17:00' tmp = "
    " + i18n("time for event,   to prevent ugly line breaks", "Time: %1"). arg( event->dtStartTimeStr().replace(" ", " ") ); } else { tmp = "
    " + i18n("time range for event,   to prevent ugly line breaks", "Time: %1 - %2"). arg( event->dtStartTimeStr().replace(" ", " ") ). arg( event->dtEndTimeStr().replace(" ", " ") ); } ret += tmp; } } return ret; } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo*todo ) { QString ret; bool floats( todo->doesFloat() ); if (todo->hasStartDate()) // 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").arg( (floats) ?(todo->dtStartDateStr().replace(" ", " ")) :(todo->dtStartStr().replace(" ", " ")) ) ; if (todo->hasDueDate()) ret += "
    " + i18n("Due: %1").arg( (floats) ?(todo->dtDueDateStr().replace(" ", " ")) :(todo->dtDueStr().replace(" ", " ")) ); if (todo->isCompleted()) ret += "
    " + i18n("Completed: %1").arg( todo->completedStr().replace(" ", " ") ); else ret += "
    " + i18n("%1 % completed").arg(todo->percentComplete()); return ret; } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal*journal ) { QString ret; if (journal->dtStart().isValid() ) { ret += "
    " + i18n("Date: %1").arg( journal->dtStartDateStr( false ) ); } return ret; } QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb ) { QString tmp( "
    " + i18n("Period start: %1") ); QString ret = tmp.arg( KGlobal::locale()->formatDateTime( fb->dtStart() ) ); tmp = "
    " + i18n("Period start: %1"); ret += tmp.arg( KGlobal::locale()->formatDateTime( fb->dtEnd() ) ); return ret; } 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 ) { mResult = "" + i18n("Free/Busy information for %1") .arg(fb->organizer().fullName()) + ""; mResult += dateRangeText( fb ); mResult += ""; return !mResult.isEmpty(); } QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence* incidence, QString dtRangeText ) { QString tmp = ""+ incidence->summary().replace("\n", "
    ")+"
    "; tmp += dtRangeText; if (!incidence->location().isEmpty()) { // Put Location: in italics tmp += "
    "+i18n("Location: %1"). arg( incidence->location().replace("\n", "
    ") ); } if (!incidence->description().isEmpty()) { QString desc(incidence->description()); if (desc.length()>120) { desc = desc.left(120) + "..."; } tmp += "
    ----------
    " + i18n("Description:
    ") + desc.replace("\n", "
    "); } tmp += "
    "; return tmp; } QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText ) { ToolTipVisitor v; if ( v.act( incidence, richText ) ) { return v.result(); } else return QString::null; } /******************************************************************* * Helper functions for the Incidence tooltips *******************************************************************/ class IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor { public: MailBodyVisitor() : mResult( "" ) {} bool act( IncidenceBase *incidence ) { 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: QString mResult; }; static QString mailBodyIncidence( Incidence *incidence ) { QString body; if ( !incidence->summary().isEmpty() ) { body += i18n("Summary: %1\n").arg( incidence->summary() ); } if ( !incidence->organizer().isEmpty() ) { body += i18n("Organizer: %1\n").arg( incidence->organizer().fullName() ); } if ( !incidence->location().isEmpty() ) { body += i18n("Location: %1\n").arg( incidence->location() ); } return body; } bool IncidenceFormatter::MailBodyVisitor::visit( Event *event ) { QString recurrence[]= {i18n("no recurrence", "None"), i18n("Minutely"), i18n("Hourly"), i18n("Daily"), i18n("Weekly"), i18n("Monthly Same Day"), i18n("Monthly Same Position"), i18n("Yearly"), i18n("Yearly"), i18n("Yearly")}; mResult = mailBodyIncidence( event ); mResult += i18n("Start Date: %1\n").arg( event->dtStartDateStr() ); if ( !event->doesFloat() ) { mResult += i18n("Start Time: %1\n").arg( event->dtStartTimeStr() ); } if ( event->dtStart() != event->dtEnd() ) { mResult += i18n("End Date: %1\n").arg( event->dtEndDateStr() ); } if ( !event->doesFloat() ) { mResult += i18n("End Time: %1\n").arg( event->dtEndTimeStr() ); } if ( event->doesRecur() ) { Recurrence *recur = event->recurrence(); // TODO: Merge these two to one of the form "Recurs every 3 days" mResult += i18n("Recurs: %1\n") .arg( recurrence[ recur->recurrenceType() ] ); mResult += i18n("Frequency: %1\n") .arg( event->recurrence()->frequency() ); if ( recur->duration() > 0 ) { mResult += i18n ("Repeats once", "Repeats %n times", recur->duration()); mResult += '\n'; } else { if ( recur->duration() != -1 ) { // TODO_Recurrence: What to do with floating QString endstr; if ( event->doesFloat() ) { endstr = KGlobal::locale()->formatDate( recur->endDate() ); } else { endstr = KGlobal::locale()->formatDateTime( recur->endDateTime() ); } mResult += i18n("Repeat until: %1\n").arg( endstr ); } else { mResult += i18n("Repeats forever\n"); } } } QString details = event->description(); if ( !details.isEmpty() ) { mResult += i18n("Details:\n%1\n").arg( details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo ) { mResult = mailBodyIncidence( todo ); if ( todo->hasStartDate() ) { mResult += i18n("Start Date: %1\n").arg( todo->dtStartDateStr() ); if ( !todo->doesFloat() ) { mResult += i18n("Start Time: %1\n").arg( todo->dtStartTimeStr() ); } } if ( todo->hasDueDate() ) { mResult += i18n("Due Date: %1\n").arg( todo->dtDueDateStr() ); if ( !todo->doesFloat() ) { mResult += i18n("Due Time: %1\n").arg( todo->dtDueTimeStr() ); } } QString details = todo->description(); if ( !details.isEmpty() ) { mResult += i18n("Details:\n%1\n").arg( details ); } return !mResult.isEmpty(); } bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal ) { mResult = mailBodyIncidence( journal ); mResult += i18n("Date: %1\n").arg( journal->dtStartDateStr() ); if ( !journal->doesFloat() ) { mResult += i18n("Time: %1\n").arg( journal->dtStartTimeStr() ); } if ( !journal->description().isEmpty() ) mResult += i18n("Text of the journal:\n%1\n").arg( journal->description() ); return !mResult.isEmpty(); } QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence ) { if ( !incidence ) return QString::null; MailBodyVisitor v; if ( v.act( incidence ) ) { return v.result(); } return QString::null; } diff --git a/kioslave/imap4/imap4.cc b/kioslave/imap4/imap4.cc index 8b565d78b..7686f2f80 100644 --- a/kioslave/imap4/imap4.cc +++ b/kioslave/imap4/imap4.cc @@ -1,2543 +1,2543 @@ /********************************************************************** * * imap4.cc - IMAP4rev1 KIOSlave * Copyright (C) 2001-2002 Michael Haeckel * Copyright (C) 1999 John Corey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Send comments and bug fixes to jcorey@fruity.ath.cx * *********************************************************************/ /** * @class IMAP4Protocol * @note References: * - RFC 2060 - Internet Message Access Protocol - Version 4rev1 - December 1996 * - RFC 2192 - IMAP URL Scheme - September 1997 * - RFC 1731 - IMAP Authentication Mechanisms - December 1994 * (Discusses KERBEROSv4, GSSAPI, and S/Key) * - RFC 2195 - IMAP/POP AUTHorize Extension for Simple Challenge/Response * - September 1997 (CRAM-MD5 authentication method) * - RFC 2104 - HMAC: Keyed-Hashing for Message Authentication - February 1997 * - RFC 2086 - IMAP4 ACL extension - January 1997 * - http://www.ietf.org/internet-drafts/draft-daboo-imap-annotatemore-05.txt * IMAP ANNOTATEMORE draft - April 2004. * * * Supported URLs: * \verbatim imap://server/ imap://user:pass@server/ imap://user;AUTH=method:pass@server/ imap://server/folder/ * \endverbatim * These URLs cause the following actions (in order): * - Prompt for user/pass, list all folders in home directory * - Uses LOGIN to log in * - Uses AUTHENTICATE to log in * - List messages in folder * * @note API notes: * Not receiving the required write access for a folder means * ERR_CANNOT_OPEN_FOR_WRITING. * ERR_DOES_NOT_EXIST is reserved for folders. */ #ifdef HAVE_CONFIG_H #include #endif #include "imap4.h" #include "rfcdecoder.h" #include #include #include #include #include #include //Added by qt3to4: #include #include #include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "kdepimmacros.h" #define IMAP_PROTOCOL "imap" #define IMAP_SSL_PROTOCOL "imaps" using namespace KIO; extern "C" { void sigalrm_handler (int); KDE_EXPORT int kdemain (int argc, char **argv); } #ifdef HAVE_LIBSASL2 static sasl_callback_t callbacks[] = { { SASL_CB_ECHOPROMPT, NULL, NULL }, { SASL_CB_NOECHOPROMPT, NULL, NULL }, { SASL_CB_GETREALM, NULL, NULL }, { SASL_CB_USER, NULL, NULL }, { SASL_CB_AUTHNAME, NULL, NULL }, { SASL_CB_PASS, NULL, NULL }, { SASL_CB_GETOPT, NULL, NULL }, { SASL_CB_CANON_USER, NULL, NULL }, { SASL_CB_LIST_END, NULL, NULL } }; #endif int kdemain (int argc, char **argv) { kdDebug(7116) << "IMAP4::kdemain" << endl; KInstance instance ("kio_imap4"); if (argc != 4) { fprintf(stderr, "Usage: kio_imap4 protocol domain-socket1 domain-socket2\n"); ::exit (-1); } #ifdef HAVE_LIBSASL2 if ( sasl_client_init( callbacks ) != SASL_OK ) { fprintf(stderr, "SASL library initialization failed!\n"); ::exit (-1); } #endif //set debug handler IMAP4Protocol *slave; if (strcasecmp (argv[1], IMAP_SSL_PROTOCOL) == 0) slave = new IMAP4Protocol (argv[2], argv[3], true); else if (strcasecmp (argv[1], IMAP_PROTOCOL) == 0) slave = new IMAP4Protocol (argv[2], argv[3], false); else abort (); slave->dispatchLoop (); delete slave; #ifdef HAVE_LIBSASL2 sasl_done(); #endif return 0; } void sigchld_handler (int signo) { int pid, status; while (true && signo == SIGCHLD) { pid = waitpid (-1, &status, WNOHANG); if (pid <= 0) { // Reinstall signal handler, since Linux resets to default after // the signal occurred ( BSD handles it different, but it should do // no harm ). signal (SIGCHLD, sigchld_handler); return; } } } IMAP4Protocol::IMAP4Protocol (const Q3CString & pool, const Q3CString & app, bool isSSL) :TCPSlaveBase ((isSSL ? 993 : 143), (isSSL ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL), pool, app, isSSL), imapParser (), mimeIO (), mySSL( isSSL ), relayEnabled( false ), cacheOutput( false ), decodeContent( false ), outputBuffer(&outputCache), outputBufferIndex(0), mProcessedSize( 0 ), readBufferLen( 0 ), mTimeOfLastNoop( QDateTime() ) { readBuffer[0] = 0x00; } IMAP4Protocol::~IMAP4Protocol () { closeDescriptor(); kdDebug(7116) << "IMAP4: Finishing" << endl; } void IMAP4Protocol::get (const KURL & _url) { if (!makeLogin()) return; kdDebug(7116) << "IMAP4::get - " << _url.prettyURL() << endl; QString aBox, aSequence, aType, aSection, aValidity, aDelimiter, aInfo; enum IMAP_TYPE aEnum = parseURL (_url, aBox, aSection, aType, aSequence, aValidity, aDelimiter, aInfo); if (aEnum != ITYPE_ATTACH) mimeType (getMimeType(aEnum)); if (aInfo == "DECODE") decodeContent = true; if (aSequence == "0:0" && getState() == ISTATE_SELECT) { imapCommand *cmd = doCommand (imapCommand::clientNoop()); completeQueue.removeRef(cmd); } if (aSequence.isEmpty ()) { aSequence = "1:*"; } mProcessedSize = 0; imapCommand *cmd = NULL; if (!assureBox (aBox, true)) return; #ifdef USE_VALIDITY if (selectInfo.uidValidityAvailable () && !aValidity.isEmpty () && selectInfo.uidValidity () != aValidity.toULong ()) { // this url is stale error (ERR_COULD_NOT_READ, _url.prettyURL()); return; } else #endif { // The "section" specified by the application can be: // * empty (which means body, size and flags) // * a known keyword, like STRUCTURE, ENVELOPE, HEADER, BODY.PEEK[...] // (in which case the slave has some logic to add the necessary items) // * Otherwise, it specifies the exact data items to request. In this case, all // the logic is in the app. QString aUpper = aSection.upper(); if (aUpper.find ("STRUCTURE") != -1) { aSection = "BODYSTRUCTURE"; } else if (aUpper.find ("ENVELOPE") != -1) { aSection = "UID RFC822.SIZE FLAGS ENVELOPE"; if (hasCapability("IMAP4rev1")) { aSection += " BODY.PEEK[HEADER.FIELDS (REFERENCES)]"; } else { // imap4 does not know HEADER.FIELDS aSection += " RFC822.HEADER.LINES (REFERENCES)"; } } else if (aUpper == "HEADER") { aSection = "UID RFC822.HEADER RFC822.SIZE FLAGS"; } else if (aUpper.find ("BODY.PEEK[") != -1) { if (aUpper.find ("BODY.PEEK[]") != -1) { if (!hasCapability("IMAP4rev1")) // imap4 does not know BODY.PEEK[] aSection.replace("BODY.PEEK[]", "RFC822.PEEK"); } aSection.prepend("UID RFC822.SIZE FLAGS "); } else if (aSection.isEmpty()) { aSection = "UID BODY[] RFC822.SIZE FLAGS"; } if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX) { // write the digest header cacheOutput = true; outputLine ("Content-Type: multipart/digest; boundary=\"IMAPDIGEST\"\r\n", 55); if (selectInfo.recentAvailable ()) outputLineStr ("X-Recent: " + QString::number(selectInfo.recent ()) + "\r\n"); if (selectInfo.countAvailable ()) outputLineStr ("X-Count: " + QString::number(selectInfo.count ()) + "\r\n"); if (selectInfo.unseenAvailable ()) outputLineStr ("X-Unseen: " + QString::number(selectInfo.unseen ()) + "\r\n"); if (selectInfo.uidValidityAvailable ()) outputLineStr ("X-uidValidity: " + QString::number(selectInfo.uidValidity ()) + "\r\n"); if (selectInfo.uidNextAvailable ()) outputLineStr ("X-UidNext: " + QString::number(selectInfo.uidNext ()) + "\r\n"); if (selectInfo.flagsAvailable ()) outputLineStr ("X-Flags: " + QString::number(selectInfo.flags ()) + "\r\n"); if (selectInfo.permanentFlagsAvailable ()) outputLineStr ("X-PermanentFlags: " + QString::number(selectInfo.permanentFlags ()) + "\r\n"); if (selectInfo.readWriteAvailable ()) { if (selectInfo.readWrite()) { outputLine ("X-Access: Read/Write\r\n", 22); } else { outputLine ("X-Access: Read only\r\n", 21); } } outputLine ("\r\n", 2); flushOutput(QString::null); cacheOutput = false; } if (aEnum == ITYPE_MSG || (aEnum == ITYPE_ATTACH && !decodeContent)) relayEnabled = true; // normal mode, relay data if (aSequence != "0:0") { QString contentEncoding; if (aEnum == ITYPE_ATTACH && decodeContent) { // get the MIME header and fill getLastHandled() QString mySection = aSection; mySection.replace("]", ".MIME]"); cmd = sendCommand (imapCommand::clientFetch (aSequence, mySection)); do { while (!parseLoop ()); } while (!cmd->isComplete ()); completeQueue.removeRef (cmd); // get the content encoding now because getLastHandled will be cleared if (getLastHandled() && getLastHandled()->getHeader()) contentEncoding = getLastHandled()->getHeader()->getEncoding(); // from here on collect the data // it is send to the client in flushOutput in one go // needed to decode the content cacheOutput = true; } cmd = sendCommand (imapCommand::clientFetch (aSequence, aSection)); int res; aUpper = aSection.upper(); do { while (!(res = parseLoop())); if (res == -1) break; mailHeader *lastone = 0; imapCache *cache = getLastHandled (); if (cache) lastone = cache->getHeader (); if (!cmd->isComplete ()) { if ((aUpper.find ("BODYSTRUCTURE") != -1) || (aUpper.find ("FLAGS") != -1) || (aUpper.find ("UID") != -1) || (aUpper.find ("ENVELOPE") != -1) || (aUpper.find ("BODY.PEEK[0]") != -1 && (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX))) { if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX) { // write the mime header (default is here message/rfc822) outputLine ("--IMAPDIGEST\r\n", 14); cacheOutput = true; if (cache->getUid () != 0) outputLineStr ("X-UID: " + QString::number(cache->getUid ()) + "\r\n"); if (cache->getSize () != 0) outputLineStr ("X-Length: " + QString::number(cache->getSize ()) + "\r\n"); if (!cache->getDate ().isEmpty()) outputLineStr ("X-Date: " + cache->getDate () + "\r\n"); if (cache->getFlags () != 0) outputLineStr ("X-Flags: " + QString::number(cache->getFlags ()) + "\r\n"); } else cacheOutput = true; if ( lastone && !decodeContent ) lastone->outputPart (*this); cacheOutput = false; flushOutput(contentEncoding); } } // if not complete } while (!cmd->isComplete ()); if (aEnum == ITYPE_BOX || aEnum == ITYPE_DIR_AND_BOX) { // write the end boundary outputLine ("--IMAPDIGEST--\r\n", 16); } completeQueue.removeRef (cmd); } } // just to keep everybody happy when no data arrived data (QByteArray ()); finished (); relayEnabled = false; cacheOutput = false; kdDebug(7116) << "IMAP4::get - finished" << endl; } void IMAP4Protocol::listDir (const KURL & _url) { kdDebug(7116) << " IMAP4::listDir - " << _url.prettyURL() << endl; if (_url.path().isEmpty()) { KURL url = _url; url.setPath("/"); redirection( url ); finished(); return; } QString myBox, mySequence, myLType, mySection, myValidity, myDelimiter, myInfo; // parseURL with caching enum IMAP_TYPE myType = parseURL (_url, myBox, mySection, myLType, mySequence, myValidity, myDelimiter, myInfo, true); if (!makeLogin()) return; if (myType == ITYPE_DIR || myType == ITYPE_DIR_AND_BOX) { QString listStr = myBox; imapCommand *cmd; if (!listStr.isEmpty () && !listStr.endsWith(myDelimiter) && mySection != "FOLDERONLY") listStr += myDelimiter; if (mySection.isEmpty()) { listStr += "%"; } else if (mySection == "COMPLETE") { listStr += "*"; } kdDebug(7116) << "IMAP4Protocol::listDir - listStr=" << listStr << endl; cmd = doCommand (imapCommand::clientList ("", listStr, (myLType == "LSUB" || myLType == "LSUBNOCHECK"))); if (cmd->result () == "OK") { QString mailboxName; UDSEntry entry; UDSAtom atom; KURL aURL = _url; if (aURL.path().find(';') != -1) aURL.setPath(aURL.path().left(aURL.path().find(';'))); kdDebug(7116) << "IMAP4Protocol::listDir - got " << listResponses.count () << endl; if (myLType == "LSUB") { // fire the same command as LIST to check if the box really exists Q3ValueList listResponsesSave = listResponses; doCommand (imapCommand::clientList ("", listStr, false)); for (Q3ValueListIterator < imapList > it = listResponsesSave.begin (); it != listResponsesSave.end (); ++it) { bool boxOk = false; for (Q3ValueListIterator < imapList > it2 = listResponses.begin (); it2 != listResponses.end (); ++it2) { if ((*it2).name() == (*it).name()) { boxOk = true; // copy the flags from the LIST-command (*it) = (*it2); break; } } if (boxOk) doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY")); else // this folder is dead kdDebug(7116) << "IMAP4Protocol::listDir - suppress " << (*it).name() << endl; } listResponses = listResponsesSave; } else // LIST or LSUBNOCHECK { for (Q3ValueListIterator < imapList > it = listResponses.begin (); it != listResponses.end (); ++it) { doListEntry (aURL, myBox, (*it), (mySection != "FOLDERONLY")); } } entry.clear (); listEntry (entry, true); } else { error (ERR_CANNOT_ENTER_DIRECTORY, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } if ((myType == ITYPE_BOX || myType == ITYPE_DIR_AND_BOX) && myLType != "LIST" && myLType != "LSUB" && myLType != "LSUBNOCHECK") { KURL aURL = _url; aURL.setQuery (QString::null); const QString encodedUrl = aURL.url(0, 106); // utf-8 if (!_url.query ().isEmpty ()) { QString query = KURL::decode_string (_url.query ()); query = query.right (query.length () - 1); if (!query.isEmpty()) { imapCommand *cmd = NULL; if (!assureBox (myBox, true)) return; if (!selectInfo.countAvailable() || selectInfo.count()) { cmd = doCommand (imapCommand::clientSearch (query)); if (cmd->result() != "OK") { error(ERR_UNSUPPORTED_ACTION, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); QStringList list = getResults (); int stretch = 0; if (selectInfo.uidNextAvailable ()) stretch = QString::number(selectInfo.uidNext ()).length (); UDSEntry entry; imapCache fake; for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { fake.setUid((*it).toULong()); doListEntry (encodedUrl, stretch, &fake); } entry.clear (); listEntry (entry, true); } } } else { if (!assureBox (myBox, true)) return; kdDebug(7116) << "IMAP4: select returned:" << endl; if (selectInfo.recentAvailable ()) kdDebug(7116) << "Recent: " << selectInfo.recent () << "d" << endl; if (selectInfo.countAvailable ()) kdDebug(7116) << "Count: " << selectInfo.count () << "d" << endl; if (selectInfo.unseenAvailable ()) kdDebug(7116) << "Unseen: " << selectInfo.unseen () << "d" << endl; if (selectInfo.uidValidityAvailable ()) kdDebug(7116) << "uidValidity: " << selectInfo.uidValidity () << "d" << endl; if (selectInfo.flagsAvailable ()) kdDebug(7116) << "Flags: " << selectInfo.flags () << "d" << endl; if (selectInfo.permanentFlagsAvailable ()) kdDebug(7116) << "PermanentFlags: " << selectInfo.permanentFlags () << "d" << endl; if (selectInfo.readWriteAvailable ()) kdDebug(7116) << "Access: " << (selectInfo.readWrite ()? "Read/Write" : "Read only") << endl; #ifdef USE_VALIDITY if (selectInfo.uidValidityAvailable () && selectInfo.uidValidity () != myValidity.toULong ()) { //redirect KURL newUrl = _url; newUrl.setPath ("/" + myBox + ";UIDVALIDITY=" + QString::number(selectInfo.uidValidity ())); kdDebug(7116) << "IMAP4::listDir - redirecting to " << newUrl.prettyURL() << endl; redirection (newUrl); } else #endif if (selectInfo.count () > 0) { int stretch = 0; if (selectInfo.uidNextAvailable ()) stretch = QString::number(selectInfo.uidNext ()).length (); // kdDebug(7116) << selectInfo.uidNext() << "d used to stretch " << stretch << endl; UDSEntry entry; if (mySequence.isEmpty()) mySequence = "1:*"; bool withSubject = mySection.isEmpty(); if (mySection.isEmpty()) mySection = "UID RFC822.SIZE ENVELOPE"; bool withFlags = mySection.upper().find("FLAGS") != -1; imapCommand *fetch = sendCommand (imapCommand:: clientFetch (mySequence, mySection)); imapCache *cache; do { while (!parseLoop ()); cache = getLastHandled (); if (cache && !fetch->isComplete()) doListEntry (encodedUrl, stretch, cache, withFlags, withSubject); } while (!fetch->isComplete ()); entry.clear (); listEntry (entry, true); } } } if ( !selectInfo.alert().isNull() ) { if ( !myBox.isEmpty() ) { warning( i18n( "Message from %1 while processing '%2': %3" ).arg( myHost, myBox, selectInfo.alert() ) ); } else { warning( i18n( "Message from %1: %2" ).arg( myHost, selectInfo.alert() ) ); } selectInfo.setAlert( 0 ); } kdDebug(7116) << "IMAP4Protocol::listDir - Finishing listDir" << endl; finished (); } void IMAP4Protocol::setHost (const QString & _host, int _port, const QString & _user, const QString & _pass) { if (myHost != _host || myPort != _port || myUser != _user || myPass != _pass) { // what's the point of doing 4 string compares to avoid 4 string copies? // DF: I guess to avoid calling closeConnection() unnecessarily. if (!myHost.isEmpty ()) closeConnection (); myHost = _host; myPort = _port; myUser = _user; myPass = _pass; // FIXME pass it in? myService = _port==143?QLatin1String( "imap" ):QLatin1String( "imaps" ); } } void IMAP4Protocol::parseRelay (const QByteArray & buffer) { if (relayEnabled) { // relay data immediately data( buffer ); mProcessedSize += buffer.size(); processedSize( mProcessedSize ); } else if (cacheOutput) { // collect data if ( !outputBuffer.isOpen() ) { outputBuffer.open(QIODevice::WriteOnly); } outputBuffer.at(outputBufferIndex); outputBuffer.writeBlock(buffer, buffer.size()); outputBufferIndex += buffer.size(); } } void IMAP4Protocol::parseRelay (ulong len) { if (relayEnabled) totalSize (len); } bool IMAP4Protocol::parseRead(QByteArray & buffer, long len, long relay) { const long int bufLen = 8192; char buf[bufLen]; // FIXME while (buffer.size() < len ) { - ssize_t readLen = myRead(buf, QMIN(len - buffer.size(), bufLen - 1)); + ssize_t readLen = myRead(buf, qMin(len - buffer.size(), bufLen - 1)); if (readLen == 0) { kdDebug(7116) << "parseRead: readLen == 0 - connection broken" << endl; error (ERR_CONNECTION_BROKEN, myHost); setState(ISTATE_CONNECT); closeConnection(); return FALSE; } if (relay > buffer.size()) { QByteArray relayData; ssize_t relbuf = relay - buffer.size(); - int currentRelay = QMIN(relbuf, readLen); + int currentRelay = qMin(relbuf, readLen); relayData.setRawData(buf, currentRelay); parseRelay(relayData); relayData.resetRawData(buf, currentRelay); } { QBuffer stream( &buffer ); stream.open (QIODevice::WriteOnly); stream.at (buffer.size ()); stream.writeBlock (buf, readLen); stream.close (); } } return (buffer.size() == len); } bool IMAP4Protocol::parseReadLine (QByteArray & buffer, long relay) { if (myHost.isEmpty()) return FALSE; while (true) { ssize_t copyLen = 0; if (readBufferLen > 0) { while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') copyLen++; if (copyLen < readBufferLen) copyLen++; if (relay > 0) { QByteArray relayData; if (copyLen < (ssize_t) relay) relay = copyLen; relayData.setRawData (readBuffer, relay); parseRelay (relayData); relayData.resetRawData (readBuffer, relay); // kdDebug(7116) << "relayed : " << relay << "d" << endl; } // append to buffer { QBuffer stream (&buffer); stream.open (QIODevice::WriteOnly); stream.at (buffer.size ()); stream.writeBlock (readBuffer, copyLen); stream.close (); // kdDebug(7116) << "appended " << copyLen << "d got now " << buffer.size() << endl; } readBufferLen -= copyLen; if (readBufferLen) memmove(readBuffer, &readBuffer[copyLen], readBufferLen); if (buffer[buffer.size() - 1] == '\n') return TRUE; } if (!isConnectionValid()) { kdDebug(7116) << "parseReadLine - connection broken" << endl; error (ERR_CONNECTION_BROKEN, myHost); setState(ISTATE_CONNECT); closeConnection(); return FALSE; } if (!waitForResponse( responseTimeout() )) { error(ERR_SERVER_TIMEOUT, myHost); setState(ISTATE_CONNECT); closeConnection(); return FALSE; } readBufferLen = read(readBuffer, IMAP_BUFFER - 1); if (readBufferLen == 0) { kdDebug(7116) << "parseReadLine: readBufferLen == 0 - connection broken" << endl; error (ERR_CONNECTION_BROKEN, myHost); setState(ISTATE_CONNECT); closeConnection(); return FALSE; } } } void IMAP4Protocol::setSubURL (const KURL & _url) { kdDebug(7116) << "IMAP4::setSubURL - " << _url.prettyURL() << endl; KIO::TCPSlaveBase::setSubURL (_url); } void IMAP4Protocol::put (const KURL & _url, int, bool, bool) { kdDebug(7116) << "IMAP4::put - " << _url.prettyURL() << endl; // KIO::TCPSlaveBase::put(_url,permissions,overwrite,resume); QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; enum IMAP_TYPE aType = parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); // see if it is a box if (aType != ITYPE_BOX && aType != ITYPE_DIR_AND_BOX) { if (aBox[aBox.length () - 1] == '/') aBox = aBox.right (aBox.length () - 1); imapCommand *cmd = doCommand (imapCommand::clientCreate (aBox)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_WRITE, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } else { Q3PtrList < QByteArray > bufferList; int length = 0; int result; // Loop until we got 'dataEnd' do { QByteArray *buffer = new QByteArray (); dataReq (); // Request for data result = readData (*buffer); if (result > 0) { bufferList.append (buffer); length += result; } else { delete buffer; } } while (result > 0); if (result != 0) { error (ERR_ABORTED, _url.prettyURL()); return; } imapCommand *cmd = sendCommand (imapCommand::clientAppend (aBox, aSection, length)); while (!parseLoop ()); // see if server is waiting if (!cmd->isComplete () && !getContinuation ().isEmpty ()) { bool sendOk = true; ulong wrote = 0; QByteArray *buffer; // send data to server while (!bufferList.isEmpty () && sendOk) { buffer = bufferList.take (0); sendOk = (write (buffer->data (), buffer->size ()) == (ssize_t) buffer->size ()); wrote += buffer->size (); processedSize(wrote); delete buffer; if (!sendOk) { error (ERR_CONNECTION_BROKEN, myHost); completeQueue.removeRef (cmd); setState(ISTATE_CONNECT); closeConnection(); return; } } parseWriteLine (""); // Wait until cmd is complete, or connection breaks. while (!cmd->isComplete () && getState() != ISTATE_NO) parseLoop (); if ( getState() == ISTATE_NO ) { // TODO KDE4: pass cmd->resultInfo() as third argument. // ERR_CONNECTION_BROKEN expects a host, no way to pass details about the problem. error( ERR_CONNECTION_BROKEN, myHost ); completeQueue.removeRef (cmd); closeConnection(); return; } else if (cmd->result () != "OK") { error( ERR_SLAVE_DEFINED, cmd->resultInfo() ); completeQueue.removeRef (cmd); return; } else { if (hasCapability("UIDPLUS")) { QString uid = cmd->resultInfo(); if (uid.find("APPENDUID") != -1) { uid = uid.section(" ", 2, 2); uid.truncate(uid.length()-1); infoMessage("UID "+uid); } } // MUST reselect to get the new message else if (aBox == getCurrentBox ()) { cmd = doCommand (imapCommand:: clientSelect (aBox, !selectInfo.readWrite ())); completeQueue.removeRef (cmd); } } } else { //error (ERR_COULD_NOT_WRITE, myHost); // Better ship the error message, e.g. "Over Quota" error (ERR_SLAVE_DEFINED, cmd->resultInfo()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } finished (); } void IMAP4Protocol::mkdir (const KURL & _url, int) { kdDebug(7116) << "IMAP4::mkdir - " << _url.prettyURL() << endl; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); kdDebug(7116) << "IMAP4::mkdir - create " << aBox << endl; imapCommand *cmd = doCommand (imapCommand::clientCreate(aBox)); if (cmd->result () != "OK") { kdDebug(7116) << "IMAP4::mkdir - " << cmd->resultInfo() << endl; error (ERR_COULD_NOT_MKDIR, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); // start a new listing to find the type of the folder enum IMAP_TYPE type = parseURL(_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); if (type == ITYPE_BOX) { bool ask = ( aInfo.find( "ASKUSER" ) != -1 ); if ( ask && messageBox(QuestionYesNo, i18n("The following folder will be created on the server: %1 " "What do you want to store in this folder?").arg( aBox ), i18n("Create Folder"), i18n("&Messages"), i18n("&Subfolders")) == KMessageBox::No ) { cmd = doCommand(imapCommand::clientDelete(aBox)); completeQueue.removeRef (cmd); cmd = doCommand(imapCommand::clientCreate(aBox + aDelimiter)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_MKDIR, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } } cmd = doCommand(imapCommand::clientSubscribe(aBox)); completeQueue.removeRef(cmd); finished (); } void IMAP4Protocol::copy (const KURL & src, const KURL & dest, int, bool overwrite) { kdDebug(7116) << "IMAP4::copy - [" << (overwrite ? "Overwrite" : "NoOverwrite") << "] " << src.prettyURL() << " -> " << dest.prettyURL() << endl; QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo; QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo; enum IMAP_TYPE sType = parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo); enum IMAP_TYPE dType = parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo); // see if we have to create anything if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX) { // this might be konqueror int sub = dBox.find (sBox); // might be moving to upper folder if (sub > 0) { KURL testDir = dest; QString subDir = dBox.right (dBox.length () - dBox.findRev ('/')); QString topDir = dBox.left (sub); testDir.setPath ("/" + topDir); dType = parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo); kdDebug(7116) << "IMAP4::copy - checking this destination " << topDir << endl; // see if this is what the user wants if (dType == ITYPE_BOX || dType == ITYPE_DIR_AND_BOX) { kdDebug(7116) << "IMAP4::copy - assuming this destination " << topDir << endl; dBox = topDir; } else { // maybe if we create a new mailbox topDir = "/" + topDir + subDir; testDir.setPath (topDir); kdDebug(7116) << "IMAP4::copy - checking this destination " << topDir << endl; dType = parseURL (testDir, topDir, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo); if (dType != ITYPE_BOX && dType != ITYPE_DIR_AND_BOX) { // ok then we'll create a mailbox imapCommand *cmd = doCommand (imapCommand::clientCreate (topDir)); // on success we'll use it, else we'll just try to create the given dir if (cmd->result () == "OK") { kdDebug(7116) << "IMAP4::copy - assuming this destination " << topDir << endl; dType = ITYPE_BOX; dBox = topDir; } else { completeQueue.removeRef (cmd); cmd = doCommand (imapCommand::clientCreate (dBox)); if (cmd->result () == "OK") dType = ITYPE_BOX; else error (ERR_COULD_NOT_WRITE, dest.prettyURL()); } completeQueue.removeRef (cmd); } } } } if (sType == ITYPE_MSG || sType == ITYPE_BOX || sType == ITYPE_DIR_AND_BOX) { //select the source box if (!assureBox(sBox, true)) return; kdDebug(7116) << "IMAP4::copy - " << sBox << " -> " << dBox << endl; //issue copy command imapCommand *cmd = doCommand (imapCommand::clientCopy (dBox, sSequence)); if (cmd->result () != "OK") { kdError(5006) << "IMAP4::copy - " << cmd->resultInfo() << endl; error (ERR_COULD_NOT_WRITE, dest.prettyURL()); completeQueue.removeRef (cmd); return; } else { if (hasCapability("UIDPLUS")) { QString uid = cmd->resultInfo(); if (uid.find("COPYUID") != -1) { uid = uid.section(" ", 2, 3); uid.truncate(uid.length()-1); infoMessage("UID "+uid); } } } completeQueue.removeRef (cmd); } else { error (ERR_ACCESS_DENIED, src.prettyURL()); return; } finished (); } void IMAP4Protocol::del (const KURL & _url, bool isFile) { kdDebug(7116) << "IMAP4::del - [" << (isFile ? "File" : "NoFile") << "] " << _url.prettyURL() << endl; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; enum IMAP_TYPE aType = parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); switch (aType) { case ITYPE_BOX: case ITYPE_DIR_AND_BOX: if (!aSequence.isEmpty ()) { if (aSequence == "*") { if (!assureBox (aBox, false)) return; imapCommand *cmd = doCommand (imapCommand::clientExpunge ()); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } else { // if open for read/write if (!assureBox (aBox, false)) return; imapCommand *cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } } else { if (getCurrentBox() == aBox) { imapCommand *cmd = doCommand(imapCommand::clientClose()); completeQueue.removeRef(cmd); setState(ISTATE_LOGIN); } // We unsubscribe, otherwise we get ghost folders on UW-IMAP imapCommand *cmd = doCommand(imapCommand::clientUnsubscribe(aBox)); completeQueue.removeRef(cmd); cmd = doCommand(imapCommand::clientDelete (aBox)); // If this doesn't work, we try to empty the mailbox first if (cmd->result () != "OK") { completeQueue.removeRef(cmd); if (!assureBox(aBox, false)) return; bool stillOk = true; if (stillOk) { imapCommand *cmd = doCommand( imapCommand::clientStore("1:*", "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") stillOk = false; completeQueue.removeRef(cmd); } if (stillOk) { imapCommand *cmd = doCommand(imapCommand::clientClose()); if (cmd->result () != "OK") stillOk = false; completeQueue.removeRef(cmd); setState(ISTATE_LOGIN); } if (stillOk) { imapCommand *cmd = doCommand (imapCommand::clientDelete(aBox)); if (cmd->result () != "OK") stillOk = false; completeQueue.removeRef(cmd); } if (!stillOk) { error (ERR_COULD_NOT_RMDIR, _url.prettyURL()); return; } } else { completeQueue.removeRef (cmd); } } break; case ITYPE_DIR: { imapCommand *cmd = doCommand (imapCommand::clientDelete (aBox)); if (cmd->result () != "OK") { error (ERR_COULD_NOT_RMDIR, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } break; case ITYPE_MSG: { // if open for read/write if (!assureBox (aBox, false)) return; imapCommand *cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", "\\DELETED")); if (cmd->result () != "OK") { error (ERR_CANNOT_DELETE, _url.prettyURL()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } break; case ITYPE_UNKNOWN: case ITYPE_ATTACH: error (ERR_CANNOT_DELETE, _url.prettyURL()); break; } finished (); } /* * Copy a mail: data = 'C' + srcURL (KURL) + destURL (KURL) * Capabilities: data = 'c'. Result shipped in infoMessage() signal * No-op: data = 'N' * Namespace: data = 'n'. Result shipped in infoMessage() signal * The format is: section=namespace=delimiter * Note that the namespace can be empty * Unsubscribe: data = 'U' + URL (KURL) * Subscribe: data = 'u' + URL (KURL) * Change the status: data = 'S' + URL (KURL) + Flags (QCString) * ACL commands: data = 'A' + command + URL (KURL) + command-dependent args * AnnotateMore commands: data = 'M' + 'G'et/'S'et + URL + entry + command-dependent args * Search: data = 'E' + URL (KURL) */ void IMAP4Protocol::special (const QByteArray & aData) { kdDebug(7116) << "IMAP4Protocol::special" << endl; if (!makeLogin()) return; QDataStream stream( aData ); int tmp; stream >> tmp; switch (tmp) { case 'C': { // copy KURL src; KURL dest; stream >> src >> dest; copy(src, dest, 0, FALSE); break; } case 'c': { // capabilities infoMessage(imapCapabilities.join(" ")); finished(); break; } case 'N': { // NOOP imapCommand *cmd = doCommand(imapCommand::clientNoop()); if (cmd->result () != "OK") { kdDebug(7116) << "NOOP did not succeed - connection broken" << endl; completeQueue.removeRef (cmd); error (ERR_CONNECTION_BROKEN, myHost); return; } completeQueue.removeRef (cmd); finished(); break; } case 'n': { // namespace in the form "section=namespace=delimiter" // entries are separated by , infoMessage( imapNamespaces.join(",") ); finished(); break; } case 'U': { // unsubscribe KURL _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); imapCommand *cmd = doCommand(imapCommand::clientUnsubscribe(aBox)); if (cmd->result () != "OK") { completeQueue.removeRef (cmd); error(ERR_SLAVE_DEFINED, i18n("Unsubscribe of folder %1 " "failed. The server returned: %2") .arg(_url.prettyURL()) .arg(cmd->resultInfo())); return; } completeQueue.removeRef (cmd); finished(); break; } case 'u': { // subscribe KURL _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); imapCommand *cmd = doCommand(imapCommand::clientSubscribe(aBox)); if (cmd->result () != "OK") { completeQueue.removeRef (cmd); error(ERR_SLAVE_DEFINED, i18n("Subscribe of folder %1 " "failed. The server returned: %2") .arg(_url.prettyURL()) .arg(cmd->resultInfo())); return; } completeQueue.removeRef (cmd); finished(); break; } case 'A': { // acl int cmd; stream >> cmd; if ( hasCapability( "ACL" ) ) { specialACLCommand( cmd, stream ); } else { error( ERR_UNSUPPORTED_ACTION, "ACL" ); } break; } case 'M': { // annotatemore int cmd; stream >> cmd; if ( hasCapability( "ANNOTATEMORE" ) ) { specialAnnotateMoreCommand( cmd, stream ); } else { error( ERR_UNSUPPORTED_ACTION, "ANNOTATEMORE" ); } break; } case 'S': { // status KURL _url; Q3CString newFlags; stream >> _url >> newFlags; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); if (!assureBox(aBox, false)) return; imapCommand *cmd = doCommand (imapCommand:: clientStore (aSequence, "-FLAGS.SILENT", "\\SEEN \\ANSWERED \\FLAGGED \\DRAFT")); if (cmd->result () != "OK") { completeQueue.removeRef (cmd); error(ERR_COULD_NOT_WRITE, i18n("Changing the flags of message %1 " "failed.").arg(_url.prettyURL())); return; } completeQueue.removeRef (cmd); if (!newFlags.isEmpty()) { cmd = doCommand (imapCommand:: clientStore (aSequence, "+FLAGS.SILENT", newFlags)); if (cmd->result () != "OK") { completeQueue.removeRef (cmd); error(ERR_COULD_NOT_WRITE, i18n("Changing the flags of message %1 " "failed.").arg(_url.prettyURL())); return; } completeQueue.removeRef (cmd); } finished(); break; } case 'E': { // search specialSearchCommand( stream ); break; } default: kdWarning(7116) << "Unknown command in special(): " << tmp << endl; error( ERR_UNSUPPORTED_ACTION, QString(QChar(tmp)) ); break; } } void IMAP4Protocol::specialACLCommand( int command, QDataStream& stream ) { // All commands start with the URL to the box KURL _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); switch( command ) { case 'S': // SETACL { QString user, acl; stream >> user >> acl; kdDebug(7116) << "SETACL " << aBox << " " << user << " " << acl << endl; imapCommand *cmd = doCommand(imapCommand::clientSetACL(aBox, user, acl)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Setting the Access Control List on folder %1 " "for user %2 failed. The server returned: %3") .arg(_url.prettyURL()) .arg(user) .arg(cmd->resultInfo())); return; } completeQueue.removeRef (cmd); finished(); break; } case 'D': // DELETEACL { QString user; stream >> user; kdDebug(7116) << "DELETEACL " << aBox << " " << user << endl; imapCommand *cmd = doCommand(imapCommand::clientDeleteACL(aBox, user)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Deleting the Access Control List on folder %1 " "for user %2 failed. The server returned: %3") .arg(_url.prettyURL()) .arg(user) .arg(cmd->resultInfo())); return; } completeQueue.removeRef (cmd); finished(); break; } case 'G': // GETACL { kdDebug(7116) << "GETACL " << aBox << endl; imapCommand *cmd = doCommand(imapCommand::clientGetACL(aBox)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 " "failed. The server returned: %2") .arg(_url.prettyURL()) .arg(cmd->resultInfo())); return; } // Returning information to the application from a special() command isn't easy. // I'm reusing the infoMessage trick seen above (for capabilities), but this // limits me to a string instead of a stringlist. I'm using space as separator, // since I don't think it can be used in login names. kdDebug(7116) << getResults() << endl; infoMessage(getResults().join( " " )); finished(); break; } case 'L': // LISTRIGHTS { // Do we need this one? It basically shows which rights are tied together, but that's all? error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); break; } case 'M': // MYRIGHTS { kdDebug(7116) << "MYRIGHTS " << aBox << endl; imapCommand *cmd = doCommand(imapCommand::clientMyRights(aBox)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Retrieving the Access Control List on folder %1 " "failed. The server returned: %2") .arg(_url.prettyURL()) .arg(cmd->resultInfo())); return; } QStringList lst = getResults(); kdDebug(7116) << "myrights results: " << lst << endl; if ( !lst.isEmpty() ) { Q_ASSERT( lst.count() == 1 ); infoMessage( lst.first() ); } finished(); break; } default: kdWarning(7116) << "Unknown special ACL command:" << command << endl; error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); } } void IMAP4Protocol::specialSearchCommand( QDataStream& stream ) { kdDebug(7116) << "IMAP4Protocol::specialSearchCommand" << endl; KURL _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); if (!assureBox(aBox, false)) return; imapCommand *cmd = doCommand (imapCommand::clientSearch( aSection )); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Searching of folder %1 " "failed. The server returned: %2") .arg(aBox) .arg(cmd->resultInfo())); return; } completeQueue.removeRef(cmd); QStringList lst = getResults(); kdDebug(7116) << "IMAP4Protocol::specialSearchCommand '" << aSection << "' returns " << lst << endl; infoMessage( lst.join( " " ) ); finished(); } void IMAP4Protocol::specialAnnotateMoreCommand( int command, QDataStream& stream ) { // All commands start with the URL to the box KURL _url; stream >> _url; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo); switch( command ) { case 'S': // SETANNOTATION { // Params: // KURL URL of the mailbox // QString entry (should be an actual entry name, no % or *; empty for server entries) // QMap attributes (name and value) QString entry; QMap attributes; stream >> entry >> attributes; kdDebug(7116) << "SETANNOTATION " << aBox << " " << entry << " " << attributes.count() << " attributes" << endl; imapCommand *cmd = doCommand(imapCommand::clientSetAnnotation(aBox, entry, attributes)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Setting the annotation %1 on folder %2 " " failed. The server returned: %3") .arg(entry) .arg(_url.prettyURL()) .arg(cmd->resultInfo())); return; } completeQueue.removeRef (cmd); finished(); break; } case 'G': // GETANNOTATION. { // Params: // KURL URL of the mailbox // QString entry (should be an actual entry name, no % or *; empty for server entries) // QStringList attributes (list of attributes to be retrieved, possibly with % or *) QString entry; QStringList attributeNames; stream >> entry >> attributeNames; kdDebug(7116) << "GETANNOTATION " << aBox << " " << entry << " " << attributeNames << endl; imapCommand *cmd = doCommand(imapCommand::clientGetAnnotation(aBox, entry, attributeNames)); if (cmd->result () != "OK") { error(ERR_SLAVE_DEFINED, i18n("Retrieving the annotation %1 on folder %2 " "failed. The server returned: %3") .arg(entry) .arg(_url.prettyURL()) .arg(cmd->resultInfo())); return; } // Returning information to the application from a special() command isn't easy. // I'm reusing the infoMessage trick seen above (for capabilities and acls), but this // limits me to a string instead of a stringlist. Let's use \r as separator. kdDebug(7116) << getResults() << endl; infoMessage(getResults().join( "\r" )); finished(); break; } default: kdWarning(7116) << "Unknown special annotate command:" << command << endl; error( ERR_UNSUPPORTED_ACTION, QString(QChar(command)) ); } } void IMAP4Protocol::rename (const KURL & src, const KURL & dest, bool overwrite) { kdDebug(7116) << "IMAP4::rename - [" << (overwrite ? "Overwrite" : "NoOverwrite") << "] " << src.prettyURL() << " -> " << dest.prettyURL() << endl; QString sBox, sSequence, sLType, sSection, sValidity, sDelimiter, sInfo; QString dBox, dSequence, dLType, dSection, dValidity, dDelimiter, dInfo; enum IMAP_TYPE sType = parseURL (src, sBox, sSection, sLType, sSequence, sValidity, sDelimiter, sInfo, false); enum IMAP_TYPE dType = parseURL (dest, dBox, dSection, dLType, dSequence, dValidity, dDelimiter, dInfo, false); if (dType == ITYPE_UNKNOWN) { switch (sType) { case ITYPE_BOX: case ITYPE_DIR: case ITYPE_DIR_AND_BOX: { if (getState() == ISTATE_SELECT && sBox == getCurrentBox()) { kdDebug(7116) << "IMAP4::rename - close " << getCurrentBox() << endl; // mailbox can only be renamed if it is closed imapCommand *cmd = doCommand (imapCommand::clientClose()); bool ok = cmd->result() == "OK"; completeQueue.removeRef(cmd); if (!ok) { error(ERR_CANNOT_RENAME, i18n("Unable to close mailbox.")); return; } setState(ISTATE_LOGIN); } imapCommand *cmd = doCommand (imapCommand::clientRename (sBox, dBox)); if (cmd->result () != "OK") { error (ERR_CANNOT_RENAME, cmd->result ()); completeQueue.removeRef (cmd); return; } completeQueue.removeRef (cmd); } break; case ITYPE_MSG: case ITYPE_ATTACH: case ITYPE_UNKNOWN: error (ERR_CANNOT_RENAME, src.prettyURL()); break; } } else { error (ERR_CANNOT_RENAME, src.prettyURL()); return; } finished (); } void IMAP4Protocol::slave_status () { bool connected = (getState() != ISTATE_NO) && isConnectionValid(); kdDebug(7116) << "IMAP4::slave_status " << connected << endl; slaveStatus ( connected ? myHost : QString::null, connected ); } void IMAP4Protocol::dispatch (int command, const QByteArray & data) { kdDebug(7116) << "IMAP4::dispatch - command=" << command << endl; KIO::TCPSlaveBase::dispatch (command, data); } void IMAP4Protocol::stat (const KURL & _url) { kdDebug(7116) << "IMAP4::stat - " << _url.prettyURL() << endl; QString aBox, aSequence, aLType, aSection, aValidity, aDelimiter, aInfo; // parseURL with caching enum IMAP_TYPE aType = parseURL (_url, aBox, aSection, aLType, aSequence, aValidity, aDelimiter, aInfo, true); UDSEntry entry; UDSAtom atom; atom.m_uds = UDS_NAME; atom.m_str = aBox; entry.append (atom); if (!aSection.isEmpty()) { if (getState() == ISTATE_SELECT && aBox == getCurrentBox()) { imapCommand *cmd = doCommand (imapCommand::clientClose()); bool ok = cmd->result() == "OK"; completeQueue.removeRef(cmd); if (!ok) { error(ERR_COULD_NOT_STAT, i18n("Unable to close mailbox.")); return; } setState(ISTATE_LOGIN); } bool ok = false; QString cmdInfo; if (aType == ITYPE_MSG || aType == ITYPE_ATTACH) ok = true; else { imapCommand *cmd = doCommand(imapCommand::clientStatus(aBox, aSection)); ok = cmd->result() == "OK"; cmdInfo = cmd->resultInfo(); completeQueue.removeRef(cmd); } if (!ok) { bool found = false; imapCommand *cmd = doCommand (imapCommand::clientList ("", aBox)); if (cmd->result () == "OK") { for (Q3ValueListIterator < imapList > it = listResponses.begin (); it != listResponses.end (); ++it) { if (aBox == (*it).name ()) found = true; } } completeQueue.removeRef (cmd); if (found) error(ERR_COULD_NOT_STAT, i18n("Unable to get information about folder %1. The server replied: %2").arg(aBox).arg(cmdInfo)); else error(KIO::ERR_DOES_NOT_EXIST, aBox); return; } if ((aSection == "UIDNEXT" && getStatus().uidNextAvailable()) || (aSection == "UNSEEN" && getStatus().unseenAvailable())) { atom.m_uds = UDS_SIZE; atom.m_str = QString::null; atom.m_long = (aSection == "UIDNEXT") ? getStatus().uidNext() : getStatus().unseen(); entry.append(atom); } } else if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX || aType == ITYPE_MSG || aType == ITYPE_ATTACH) { ulong validity = 0; // see if the box is already in select/examine state if (aBox == getCurrentBox ()) validity = selectInfo.uidValidity (); else { // do a status lookup on the box // only do this if the box is not selected // the server might change the validity for new select/examine imapCommand *cmd = doCommand (imapCommand::clientStatus (aBox, "UIDVALIDITY")); completeQueue.removeRef (cmd); validity = getStatus ().uidValidity (); } validity = 0; // temporary if (aType == ITYPE_BOX || aType == ITYPE_DIR_AND_BOX) { // has no or an invalid uidvalidity if (validity > 0 && validity != aValidity.toULong ()) { //redirect KURL newUrl = _url; newUrl.setPath ("/" + aBox + ";UIDVALIDITY=" + QString::number(validity)); kdDebug(7116) << "IMAP4::stat - redirecting to " << newUrl.prettyURL() << endl; redirection (newUrl); } } else if (aType == ITYPE_MSG || aType == ITYPE_ATTACH) { //must determine if this message exists //cause konqueror will check this on paste operations // has an invalid uidvalidity // or no messages in box if (validity > 0 && validity != aValidity.toULong ()) { aType = ITYPE_UNKNOWN; kdDebug(7116) << "IMAP4::stat - url has invalid validity [" << validity << "d] " << _url.prettyURL() << endl; } } } atom.m_uds = UDS_MIME_TYPE; atom.m_str = getMimeType (aType); entry.append (atom); kdDebug(7116) << "IMAP4: stat: " << atom.m_str << endl; switch (aType) { case ITYPE_DIR: atom.m_uds = UDS_FILE_TYPE; atom.m_str = QString::null; atom.m_long = S_IFDIR; entry.append (atom); break; case ITYPE_BOX: case ITYPE_DIR_AND_BOX: atom.m_uds = UDS_FILE_TYPE; atom.m_str = QString::null; atom.m_long = S_IFDIR; entry.append (atom); break; case ITYPE_MSG: case ITYPE_ATTACH: atom.m_uds = UDS_FILE_TYPE; atom.m_str = QString::null; atom.m_long = S_IFREG; entry.append (atom); break; case ITYPE_UNKNOWN: error (ERR_DOES_NOT_EXIST, _url.prettyURL()); break; } statEntry (entry); kdDebug(7116) << "IMAP4::stat - Finishing stat" << endl; finished (); } void IMAP4Protocol::openConnection() { if (makeLogin()) connected(); } void IMAP4Protocol::closeConnection() { if (getState() == ISTATE_NO) return; if (getState() == ISTATE_SELECT && metaData("expunge") == "auto") { imapCommand *cmd = doCommand (imapCommand::clientExpunge()); completeQueue.removeRef (cmd); } if (getState() != ISTATE_CONNECT) { imapCommand *cmd = doCommand (imapCommand::clientLogout()); completeQueue.removeRef (cmd); } closeDescriptor(); setState(ISTATE_NO); completeQueue.clear(); sentQueue.clear(); lastHandled = 0; currentBox = QString::null; readBufferLen = 0; } bool IMAP4Protocol::makeLogin () { if (getState () == ISTATE_LOGIN || getState () == ISTATE_SELECT) return true; kdDebug(7116) << "IMAP4::makeLogin - checking login" << endl; bool alreadyConnected = getState() == ISTATE_CONNECT; kdDebug(7116) << "IMAP4::makeLogin - alreadyConnected " << alreadyConnected << endl; if (alreadyConnected || connectToHost (myHost, myService)) { // fcntl (m_iSock, F_SETFL, (fcntl (m_iSock, F_GETFL) | O_NDELAY)); setState(ISTATE_CONNECT); myAuth = metaData("auth"); myTLS = metaData("tls"); kdDebug(7116) << "myAuth: " << myAuth << endl; imapCommand *cmd; unhandled.clear (); if (!alreadyConnected) while (!parseLoop ()); //get greeting QString greeting; if (!unhandled.isEmpty()) greeting = unhandled.first().stripWhiteSpace(); unhandled.clear (); //get rid of it cmd = doCommand (new imapCommand ("CAPABILITY", "")); kdDebug(7116) << "IMAP4: setHost: capability" << endl; for (QStringList::Iterator it = imapCapabilities.begin (); it != imapCapabilities.end (); ++it) { kdDebug(7116) << "'" << (*it) << "'" << endl; } completeQueue.removeRef (cmd); if (!hasCapability("IMAP4") && !hasCapability("IMAP4rev1")) { error(ERR_COULD_NOT_LOGIN, i18n("The server %1 supports neither " "IMAP4 nor IMAP4rev1.\nIt identified itself with: %2") .arg(myHost).arg(greeting)); closeConnection(); return false; } if (metaData("nologin") == "on") return TRUE; if (myTLS == "on" && !hasCapability(QString("STARTTLS"))) { error(ERR_COULD_NOT_LOGIN, i18n("The server does not support TLS.\n" "Disable this security feature to connect unencrypted.")); closeConnection(); return false; } if ((myTLS == "on" || (canUseTLS() && myTLS != "off")) && hasCapability(QString("STARTTLS"))) { imapCommand *cmd = doCommand (imapCommand::clientStartTLS()); if (cmd->result () == "OK") { completeQueue.removeRef(cmd); int tlsrc = startTLS(); if (tlsrc == 1) { kdDebug(7116) << "TLS mode has been enabled." << endl; imapCommand *cmd2 = doCommand (new imapCommand ("CAPABILITY", "")); for (QStringList::Iterator it = imapCapabilities.begin (); it != imapCapabilities.end (); ++it) { kdDebug(7116) << "'" << (*it) << "'" << endl; } completeQueue.removeRef (cmd2); } else { kdWarning(7116) << "TLS mode setup has failed. Aborting." << endl; error (ERR_COULD_NOT_LOGIN, i18n("Starting TLS failed.")); closeConnection(); return false; } } else completeQueue.removeRef(cmd); } if (!myAuth.isEmpty () && myAuth != "*" && !hasCapability (QString ("AUTH=") + myAuth)) { error (ERR_COULD_NOT_LOGIN, i18n("The authentication method %1 is not " "supported by the server.").arg(myAuth)); closeConnection(); return false; } if ( greeting.contains( QRegExp( "Cyrus IMAP4 v2.1" ) ) ) { removeCapability( "ANNOTATEMORE" ); } kdDebug(7116) << "IMAP4::makeLogin - attempting login" << endl; KIO::AuthInfo authInfo; authInfo.username = myUser; authInfo.password = myPass; authInfo.prompt = i18n ("Username and password for your IMAP account:"); kdDebug(7116) << "IMAP4::makeLogin - open_PassDlg said user=" << myUser << " pass=xx" << endl; QString resultInfo; if (myAuth.isEmpty () || myAuth == "*") { if (myUser.isEmpty () || myPass.isEmpty ()) { if(openPassDlg (authInfo)) { myUser = authInfo.username; myPass = authInfo.password; } } if (!clientLogin (myUser, myPass, resultInfo)) error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to login. Probably the " "password is wrong.\nThe server %1 replied:\n%2").arg(myHost).arg(resultInfo)); } else { #ifdef HAVE_LIBSASL2 if (!clientAuthenticate (this, authInfo, myHost, myAuth, mySSL, resultInfo)) error(KIO::ERR_COULD_NOT_LOGIN, i18n("Unable to authenticate via %1.\n" "The server %2 replied:\n%3").arg(myAuth).arg(myHost).arg(resultInfo)); else { myUser = authInfo.username; myPass = authInfo.password; } #else error(KIO::ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into kio_imap4.")); #endif } if ( hasCapability("NAMESPACE") ) { // get all namespaces and save the namespace - delimiter association cmd = doCommand( imapCommand::clientNamespace() ); if (cmd->result () == "OK") { kdDebug(7116) << "makeLogin - registered namespaces" << endl; } completeQueue.removeRef (cmd); } // get the default delimiter (empty listing) cmd = doCommand( imapCommand::clientList("", "") ); if (cmd->result () == "OK") { Q3ValueListIterator < imapList > it = listResponses.begin(); if ( it != listResponses.end() ) { namespaceToDelimiter[QString::null] = (*it).hierarchyDelimiter(); kdDebug(7116) << "makeLogin - delimiter for empty ns='" << (*it).hierarchyDelimiter() << "'" << endl; if ( !hasCapability("NAMESPACE") ) { // server does not support namespaces QString nsentry = QString::number( 0 ) + "==" + (*it).hierarchyDelimiter(); imapNamespaces.append( nsentry ); } } } completeQueue.removeRef (cmd); } else { kdDebug(7116) << "makeLogin - NO login" << endl; } return getState() == ISTATE_LOGIN; } void IMAP4Protocol::parseWriteLine (const QString & aStr) { //kdDebug(7116) << "Writing: " << aStr << endl; Q3CString writer = aStr.utf8(); int len = writer.length(); // append CRLF if necessary if (len == 0 || (writer[len - 1] != '\n')) { len += 2; writer += "\r\n"; } // write it write(writer.data(), len); } QString IMAP4Protocol::getMimeType (enum IMAP_TYPE aType) { switch (aType) { case ITYPE_DIR: return "inode/directory"; break; case ITYPE_BOX: return "message/digest"; break; case ITYPE_DIR_AND_BOX: return "message/directory"; break; case ITYPE_MSG: return "message/rfc822"; break; // this should be handled by flushOutput case ITYPE_ATTACH: return "application/octet-stream"; break; case ITYPE_UNKNOWN: default: return "unknown/unknown"; } } void IMAP4Protocol::doListEntry (const KURL & _url, int stretch, imapCache * cache, bool withFlags, bool withSubject) { KURL aURL = _url; aURL.setQuery (QString::null); const QString encodedUrl = aURL.url(0, 106); // utf-8 doListEntry(encodedUrl, stretch, cache, withFlags, withSubject); } void IMAP4Protocol::doListEntry (const QString & encodedUrl, int stretch, imapCache * cache, bool withFlags, bool withSubject) { if (cache) { UDSEntry entry; UDSAtom atom; entry.clear (); const QString uid = QString::number(cache->getUid()); atom.m_uds = UDS_NAME; atom.m_str = uid; atom.m_long = 0; if (stretch > 0) { atom.m_str = "0000000000000000" + atom.m_str; atom.m_str = atom.m_str.right (stretch); } if (withSubject) { mailHeader *header = cache->getHeader(); if (header) atom.m_str += " " + header->getSubject(); } entry.append (atom); atom.m_uds = UDS_URL; atom.m_str = encodedUrl; // utf-8 if (atom.m_str[atom.m_str.length () - 1] != '/') atom.m_str += '/'; atom.m_str += ";UID=" + uid; atom.m_long = 0; entry.append (atom); atom.m_uds = UDS_FILE_TYPE; atom.m_str = QString::null; atom.m_long = S_IFREG; entry.append (atom); atom.m_uds = UDS_SIZE; atom.m_long = cache->getSize(); entry.append (atom); atom.m_uds = UDS_MIME_TYPE; atom.m_str = "message/rfc822"; atom.m_long = 0; entry.append (atom); atom.m_uds = UDS_USER; atom.m_str = myUser; entry.append (atom); atom.m_uds = KIO::UDS_ACCESS; atom.m_long = (withFlags) ? cache->getFlags() : S_IRUSR | S_IXUSR | S_IWUSR; entry.append (atom); listEntry (entry, false); } } void IMAP4Protocol::doListEntry (const KURL & _url, const QString & myBox, const imapList & item, bool appendPath) { KURL aURL = _url; aURL.setQuery (QString::null); UDSEntry entry; UDSAtom atom; int hdLen = item.hierarchyDelimiter().length(); { // mailboxName will be appended to the path if appendPath is true QString mailboxName = item.name (); // some beautification if (mailboxName.find (myBox) == 0 && mailboxName.length() > myBox.length()) { mailboxName = mailboxName.right (mailboxName.length () - myBox.length ()); } if (mailboxName[0] == '/') mailboxName = mailboxName.right (mailboxName.length () - 1); if (mailboxName.left(hdLen) == item.hierarchyDelimiter()) mailboxName = mailboxName.right(mailboxName.length () - hdLen); if (mailboxName.right(hdLen) == item.hierarchyDelimiter()) mailboxName.truncate(mailboxName.length () - hdLen); atom.m_uds = UDS_NAME; if (!item.hierarchyDelimiter().isEmpty() && mailboxName.find(item.hierarchyDelimiter()) != -1) atom.m_str = mailboxName.section(item.hierarchyDelimiter(), -1); else atom.m_str = mailboxName; // konqueror will die with an assertion failure otherwise if (atom.m_str.isEmpty ()) atom.m_str = ".."; if (!atom.m_str.isEmpty ()) { atom.m_long = 0; entry.append (atom); if (!item.noSelect ()) { atom.m_uds = UDS_MIME_TYPE; if (!item.noInferiors ()) { atom.m_str = "message/directory"; } else { atom.m_str = "message/digest"; } atom.m_long = 0; entry.append (atom); mailboxName += '/'; // explicitly set this as a directory for KFileDialog atom.m_uds = UDS_FILE_TYPE; atom.m_str = QString::null; atom.m_long = S_IFDIR; entry.append (atom); } else if (!item.noInferiors ()) { atom.m_uds = UDS_MIME_TYPE; atom.m_str = "inode/directory"; atom.m_long = 0; entry.append (atom); mailboxName += '/'; // explicitly set this as a directory for KFileDialog atom.m_uds = UDS_FILE_TYPE; atom.m_str = QString::null; atom.m_long = S_IFDIR; entry.append (atom); } else { atom.m_uds = UDS_MIME_TYPE; atom.m_str = "unknown/unknown"; atom.m_long = 0; entry.append (atom); } atom.m_uds = UDS_URL; QString path = aURL.path(); atom.m_str = aURL.url (0, 106); // utf-8 if (appendPath) { if (path[path.length() - 1] == '/' && !path.isEmpty() && path != "/") path.truncate(path.length() - 1); if (!path.isEmpty() && path != "/" && path.right(hdLen) != item.hierarchyDelimiter()) { path += item.hierarchyDelimiter(); } path += mailboxName; } aURL.setPath(path); atom.m_str = aURL.url(0, 106); // utf-8 atom.m_long = 0; entry.append (atom); atom.m_uds = UDS_USER; atom.m_str = myUser; entry.append (atom); atom.m_uds = UDS_ACCESS; atom.m_long = S_IRUSR | S_IXUSR | S_IWUSR; entry.append (atom); atom.m_uds = UDS_EXTRA; atom.m_str = item.attributesAsString(); atom.m_long = 0; entry.append (atom); listEntry (entry, false); } } } enum IMAP_TYPE IMAP4Protocol::parseURL (const KURL & _url, QString & _box, QString & _section, QString & _type, QString & _uid, QString & _validity, QString & _hierarchyDelimiter, QString & _info, bool cache) { enum IMAP_TYPE retVal; retVal = ITYPE_UNKNOWN; imapParser::parseURL (_url, _box, _section, _type, _uid, _validity, _info); // kdDebug(7116) << "URL: query - '" << KURL::decode_string(_url.query()) << "'" << endl; // get the delimiter QString myNamespace = namespaceForBox( _box ); kdDebug(7116) << "IMAP4::parseURL - namespace=" << myNamespace << endl; if ( namespaceToDelimiter.contains(myNamespace) ) { _hierarchyDelimiter = namespaceToDelimiter[myNamespace]; kdDebug(7116) << "IMAP4::parseURL - delimiter=" << _hierarchyDelimiter << endl; } if (!_box.isEmpty ()) { kdDebug(7116) << "IMAP4::parseURL - box=" << _box << endl; if (makeLogin ()) { if (getCurrentBox () != _box || _type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK") { if ( cache ) { // assume a normal box retVal = ITYPE_DIR_AND_BOX; } else { // start a listing for the box to get the type imapCommand *cmd; cmd = doCommand (imapCommand::clientList ("", _box)); if (cmd->result () == "OK") { for (Q3ValueListIterator < imapList > it = listResponses.begin (); it != listResponses.end (); ++it) { //kdDebug(7116) << "IMAP4::parseURL - checking " << _box << " to " << (*it).name() << endl; if (_box == (*it).name ()) { if ( !(*it).hierarchyDelimiter().isEmpty() ) _hierarchyDelimiter = (*it).hierarchyDelimiter(); if ((*it).noSelect ()) { retVal = ITYPE_DIR; } else if ((*it).noInferiors ()) { retVal = ITYPE_BOX; } else { retVal = ITYPE_DIR_AND_BOX; } } } // if we got no list response for the box see if it's a prefix if ( retVal == ITYPE_UNKNOWN && namespaceToDelimiter.contains(_box) ) { retVal = ITYPE_DIR; } } else { kdDebug(7116) << "IMAP4::parseURL - got error for " << _box << endl; } completeQueue.removeRef (cmd); } // cache } else // current == box { retVal = ITYPE_BOX; } } else kdDebug(7116) << "IMAP4::parseURL: no login!" << endl; } else // empty box { // the root is just a dir kdDebug(7116) << "IMAP4: parseURL: box [root]" << endl; retVal = ITYPE_DIR; } // see if it is a real sequence or a simple uid if (retVal == ITYPE_BOX || retVal == ITYPE_DIR_AND_BOX) { if (!_uid.isEmpty ()) { if (_uid.find (':') == -1 && _uid.find (',') == -1 && _uid.find ('*') == -1) retVal = ITYPE_MSG; } } if (retVal == ITYPE_MSG) { if ( (_section.find ("BODY.PEEK[", 0, false) != -1 || _section.find ("BODY[", 0, false) != -1) && _section.find(".MIME") == -1 && _section.find(".HEADER") == -1 ) retVal = ITYPE_ATTACH; } if ( _hierarchyDelimiter.isEmpty() && (_type == "LIST" || _type == "LSUB" || _type == "LSUBNOCHECK") ) { // this shouldn't happen but when the delimiter is really empty // we try to reconstruct it from the URL if (!_box.isEmpty()) { int start = _url.path().findRev(_box); if (start != -1) _hierarchyDelimiter = _url.path().mid(start-1, start); kdDebug(7116) << "IMAP4::parseURL - reconstructed delimiter:" << _hierarchyDelimiter << " from URL " << _url.path() << endl; } if (_hierarchyDelimiter.isEmpty()) _hierarchyDelimiter = "/"; } kdDebug(7116) << "IMAP4::parseURL - return " << retVal << endl; return retVal; } int IMAP4Protocol::outputLine (const Q3CString & _str, int len) { if (len == -1) { len = _str.length(); } if (cacheOutput) { if ( !outputBuffer.isOpen() ) { outputBuffer.open(QIODevice::WriteOnly); } outputBuffer.at(outputBufferIndex); outputBuffer.writeBlock(_str.data(), len); outputBufferIndex += len; return 0; } QByteArray temp; bool relay = relayEnabled; relayEnabled = true; temp.setRawData (_str.data (), len); parseRelay (temp); temp.resetRawData (_str.data (), len); relayEnabled = relay; return 0; } void IMAP4Protocol::flushOutput(QString contentEncoding) { // send out cached data to the application if (outputBufferIndex == 0) return; outputBuffer.close(); outputCache.resize(outputBufferIndex); if (decodeContent) { // get the coding from the MIME header QByteArray decoded; if (contentEncoding.find("quoted-printable", 0, false) == 0) decoded = KCodecs::quotedPrintableDecode(outputCache); else if (contentEncoding.find("base64", 0, false) == 0) KCodecs::base64Decode(outputCache, decoded); else decoded = outputCache; QString mimetype = KMimeType::findByContent( decoded )->name(); kdDebug(7116) << "IMAP4::flushOutput - mimeType " << mimetype << endl; mimeType(mimetype); decodeContent = false; data( decoded ); } else { data( outputCache ); } mProcessedSize += outputBufferIndex; processedSize( mProcessedSize ); outputBufferIndex = 0; outputCache[0] = '\0'; outputBuffer.setBuffer(&outputCache); } ssize_t IMAP4Protocol::myRead(void *data, ssize_t len) { if (readBufferLen) { ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen; memcpy(data, readBuffer, copyLen); readBufferLen -= copyLen; if (readBufferLen) memcpy(readBuffer, &readBuffer[copyLen], readBufferLen); return copyLen; } if (!isConnectionValid()) return 0; waitForResponse( responseTimeout() ); return read((char*)data, len); } bool IMAP4Protocol::assureBox (const QString & aBox, bool readonly) { if (aBox.isEmpty()) return false; imapCommand *cmd = 0; if (aBox != getCurrentBox () || (!getSelected().readWrite() && !readonly)) { // open the box with the appropriate mode kdDebug(7116) << "IMAP4Protocol::assureBox - opening box" << endl; selectInfo = imapInfo(); cmd = doCommand (imapCommand::clientSelect (aBox, readonly)); bool ok = cmd->result() == "OK"; QString cmdInfo = cmd->resultInfo(); completeQueue.removeRef (cmd); if (!ok) { bool found = false; cmd = doCommand (imapCommand::clientList ("", aBox)); if (cmd->result () == "OK") { for (Q3ValueListIterator < imapList > it = listResponses.begin (); it != listResponses.end (); ++it) { if (aBox == (*it).name ()) found = true; } } completeQueue.removeRef (cmd); if (found) { if (cmdInfo.find("permission", 0, false) != -1) { // not allowed to enter this folder error(ERR_ACCESS_DENIED, cmdInfo); } else { error(ERR_SLAVE_DEFINED, i18n("Unable to open folder %1. The server replied: %2").arg(aBox).arg(cmdInfo)); } } else { error(KIO::ERR_DOES_NOT_EXIST, aBox); } return false; } } else { // Give the server a chance to deliver updates every ten seconds. // Doing this means a server roundtrip and since assureBox is called // after every mail, we do it with a timeout. kdDebug(7116) << "IMAP4Protocol::assureBox - reusing box" << endl; if ( mTimeOfLastNoop.secsTo( QDateTime::currentDateTime() ) > 10 ) { cmd = doCommand (imapCommand::clientNoop ()); completeQueue.removeRef (cmd); mTimeOfLastNoop = QDateTime::currentDateTime(); kdDebug(7116) << "IMAP4Protocol::assureBox - noop timer fired" << endl; } } // if it is the mode we want if (!getSelected().readWrite() && !readonly) { error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, aBox); return false; } return true; } diff --git a/kioslave/imap4/imapparser.cc b/kioslave/imap4/imapparser.cc index 462adc4be..5e7b1c3d8 100644 --- a/kioslave/imap4/imapparser.cc +++ b/kioslave/imap4/imapparser.cc @@ -1,1961 +1,1961 @@ /********************************************************************** * * imapparser.cc - IMAP4rev1 Parser * Copyright (C) 2001-2002 Michael Haeckel * Copyright (C) 2000 s.carstens@gmx.de * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Send comments and bug fixes to s.carstens@gmx.de * *********************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include "rfcdecoder.h" #include "imapparser.h" #include "imapinfo.h" #include "mailheader.h" #include "mimeheader.h" #include "mailaddress.h" #include #include #include //Added by qt3to4: #include #include #include #ifdef HAVE_LIBSASL2 extern "C" { #include } #endif #include #include #include #include #include #include #include #include #include imapParser::imapParser () { sentQueue.setAutoDelete (false); completeQueue.setAutoDelete (true); currentState = ISTATE_NO; commandCounter = 0; lastHandled = 0; } imapParser::~imapParser () { delete lastHandled; lastHandled = 0; } imapCommand * imapParser::doCommand (imapCommand * aCmd) { int pl = 0; sendCommand (aCmd); while (pl != -1 && !aCmd->isComplete ()) { while ((pl = parseLoop ()) == 0) ; } return aCmd; } imapCommand * imapParser::sendCommand (imapCommand * aCmd) { aCmd->setId (QString::number(commandCounter++)); sentQueue.append (aCmd); continuation.resize(0); const QString& command = aCmd->command(); if (command == "SELECT" || command == "EXAMINE") { // we need to know which box we are selecting parseString p; p.fromString(aCmd->parameter()); currentBox = parseOneWordC(p); kdDebug(7116) << "imapParser::sendCommand - setting current box to " << currentBox << endl; } else if (command == "CLOSE") { // we no longer have a box open currentBox = QString::null; } else if (command.find ("SEARCH") != -1 || command == "GETACL" || command == "LISTRIGHTS" || command == "MYRIGHTS" || command == "GETANNOTATION" || command == "NAMESPACE") { lastResults.clear (); } else if (command == "LIST" || command == "LSUB") { listResponses.clear (); } parseWriteLine (aCmd->getStr ()); return aCmd; } bool imapParser::clientLogin (const QString & aUser, const QString & aPass, QString & resultInfo) { imapCommand *cmd; bool retVal = false; cmd = doCommand (new imapCommand ("LOGIN", "\"" + rfcDecoder::quoteIMAP(aUser) + "\" \"" + rfcDecoder::quoteIMAP(aPass) + "\"")); if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeRef (cmd); return retVal; } #ifdef HAVE_LIBSASL2 static bool sasl_interact( KIO::SlaveBase *slave, KIO::AuthInfo &ai, void *in ) { kdDebug(7116) << "sasl_interact" << endl; sasl_interact_t *interact = ( sasl_interact_t * ) in; //some mechanisms do not require username && pass, so it doesn't need a popup //window for getting this info for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { if ( interact->id == SASL_CB_AUTHNAME || interact->id == SASL_CB_PASS ) { if ( ai.username.isEmpty() || ai.password.isEmpty() ) { if (!slave->openPassDlg(ai)) return false; } break; } } interact = ( sasl_interact_t * ) in; while( interact->id != SASL_CB_LIST_END ) { kdDebug(7116) << "SASL_INTERACT id: " << interact->id << endl; switch( interact->id ) { case SASL_CB_USER: case SASL_CB_AUTHNAME: kdDebug(7116) << "SASL_CB_[USER|AUTHNAME]: '" << ai.username << "'" << endl; interact->result = strdup( ai.username.utf8() ); interact->len = strlen( (const char *) interact->result ); break; case SASL_CB_PASS: kdDebug(7116) << "SASL_CB_PASS: [hidden] " << endl; interact->result = strdup( ai.password.utf8() ); interact->len = strlen( (const char *) interact->result ); break; default: interact->result = 0; interact->len = 0; break; } interact++; } return true; } #endif bool imapParser::clientAuthenticate ( KIO::SlaveBase *slave, KIO::AuthInfo &ai, const QString & aFQDN, const QString & aAuth, bool isSSL, QString & resultInfo) { bool retVal = false; #ifdef HAVE_LIBSASL2 int result; sasl_conn_t *conn = 0; sasl_interact_t *client_interact = 0; const char *out = 0; uint outlen = 0; const char *mechusing = 0; QByteArray tmp, challenge; kdDebug(7116) << "aAuth: " << aAuth << " FQDN: " << aFQDN << " isSSL: " << isSSL << endl; // see if server supports this authenticator if (!hasCapability ("AUTH=" + aAuth)) return false; // result = sasl_client_new( isSSL ? "imaps" : "imap", result = sasl_client_new( "imap", /* FIXME: with cyrus-imapd, even imaps' digest-uri must be 'imap'. I don't know if it's good or bad. */ aFQDN.latin1(), 0, 0, 0, 0, &conn ); if ( result != SASL_OK ) { kdDebug(7116) << "sasl_client_new failed with: " << result << endl; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); return false; } do { result = sasl_client_start(conn, aAuth.latin1(), &client_interact, hasCapability("SASL-IR") ? &out : 0, &outlen, &mechusing); if ( result == SASL_INTERACT ) { if ( !sasl_interact( slave, ai, client_interact ) ) { sasl_dispose( &conn ); return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kdDebug(7116) << "sasl_client_start failed with: " << result << endl; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } imapCommand *cmd; tmp.setRawData( out, outlen ); KCodecs::base64Encode( tmp, challenge ); tmp.resetRawData( out, outlen ); // then lets try it QString firstCommand = aAuth; if ( !challenge.isEmpty() ) { firstCommand += " "; firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); } cmd = sendCommand (new imapCommand ("AUTHENTICATE", firstCommand.latin1())); while ( true ) { //read the next line while (parseLoop() == 0); if ( cmd->isComplete() ) break; if (!continuation.isEmpty()) { // kdDebug(7116) << "S: " << QCString(continuation.data(),continuation.size()+1) << endl; if ( continuation.size() > 4 ) { tmp.setRawData( continuation.data() + 2, continuation.size() - 4 ); KCodecs::base64Decode( tmp, challenge ); // kdDebug(7116) << "S-1: " << QCString(challenge.data(),challenge.size()+1) << endl; tmp.resetRawData( continuation.data() + 2, continuation.size() - 4 ); } do { result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), challenge.size(), &client_interact, &out, &outlen); if (result == SASL_INTERACT) { if ( !sasl_interact( slave, ai, client_interact ) ) { sasl_dispose( &conn ); return false; } } } while ( result == SASL_INTERACT ); if ( result != SASL_CONTINUE && result != SASL_OK ) { kdDebug(7116) << "sasl_client_step failed with: " << result << endl; resultInfo = QString::fromUtf8( sasl_errdetail( conn ) ); sasl_dispose( &conn ); return false; } tmp.setRawData( out, outlen ); // kdDebug(7116) << "C-1: " << QCString(tmp.data(),tmp.size()+1) << endl; KCodecs::base64Encode( tmp, challenge ); tmp.resetRawData( out, outlen ); // kdDebug(7116) << "C: " << QCString(challenge.data(),challenge.size()+1) << endl; parseWriteLine (challenge); continuation.resize(0); } } if (cmd->result () == "OK") { currentState = ISTATE_LOGIN; retVal = true; } resultInfo = cmd->resultInfo(); completeQueue.removeRef (cmd); sasl_dispose( &conn ); //we don't use sasl_en/decode(), so it's safe to dispose the connection. #endif //HAVE_LIBSASL2 return retVal; } void imapParser::parseUntagged (parseString & result) { //kdDebug(7116) << "imapParser::parseUntagged - '" << result.cstr() << "'" << endl; parseOneWordC(result); // * QByteArray what = parseLiteral (result); // see whats coming next switch (what[0]) { //the status responses case 'B': // BAD or BYE if (qstrncmp(what, "BAD", what.size()) == 0) { parseResult (what, result); } else if (qstrncmp(what, "BYE", what.size()) == 0) { parseResult (what, result); if ( sentQueue.count() ) { // BYE that interrupts a command -> copy the reason for it imapCommand *current = sentQueue.at (0); current->setResultInfo(result.cstr()); } currentState = ISTATE_NO; } break; case 'N': // NO if (what[1] == 'O' && what.size() == 2) { parseResult (what, result); } else if (qstrncmp(what, "NAMESPACE", what.size()) == 0) { parseNamespace (result); } break; case 'O': // OK if (what[1] == 'K' && what.size() == 2) { parseResult (what, result); } break; case 'P': // PREAUTH if (qstrncmp(what, "PREAUTH", what.size()) == 0) { parseResult (what, result); currentState = ISTATE_LOGIN; } break; // parse the other responses case 'C': // CAPABILITY if (qstrncmp(what, "CAPABILITY", what.size()) == 0) { parseCapability (result); } break; case 'F': // FLAGS if (qstrncmp(what, "FLAGS", what.size()) == 0) { parseFlags (result); } break; case 'L': // LIST or LSUB or LISTRIGHTS if (qstrncmp(what, "LIST", what.size()) == 0) { parseList (result); } else if (qstrncmp(what, "LSUB", what.size()) == 0) { parseLsub (result); } else if (qstrncmp(what, "LISTRIGHTS", what.size()) == 0) { parseListRights (result); } break; case 'M': // MYRIGHTS if (qstrncmp(what, "MYRIGHTS", what.size()) == 0) { parseMyRights (result); } break; case 'S': // SEARCH or STATUS if (qstrncmp(what, "SEARCH", what.size()) == 0) { parseSearch (result); } else if (qstrncmp(what, "STATUS", what.size()) == 0) { parseStatus (result); } break; case 'A': // ACL or ANNOTATION if (qstrncmp(what, "ACL", what.size()) == 0) { parseAcl (result); } else if (qstrncmp(what, "ANNOTATION", what.size()) == 0) { parseAnnotation (result); } break; default: //better be a number { ulong number; bool valid; number = Q3CString(what, what.size() + 1).toUInt(&valid); if (valid) { what = parseLiteral (result); switch (what[0]) { case 'E': if (qstrncmp(what, "EXISTS", what.size()) == 0) { parseExists (number, result); } else if (qstrncmp(what, "EXPUNGE", what.size()) == 0) { parseExpunge (number, result); } break; case 'F': if (qstrncmp(what, "FETCH", what.size()) == 0) { seenUid = QString::null; parseFetch (number, result); } break; case 'S': if (qstrncmp(what, "STORE", what.size()) == 0) // deprecated store { seenUid = QString::null; parseFetch (number, result); } break; case 'R': if (qstrncmp(what, "RECENT", what.size()) == 0) { parseRecent (number, result); } break; default: break; } } } break; } //switch } //func void imapParser::parseResult (QByteArray & result, parseString & rest, const QString & command) { if (command == "SELECT") selectInfo.setReadWrite(true); if (rest[0] == '[') { rest.pos++; Q3CString option = parseOneWordC(rest, TRUE); switch (option[0]) { case 'A': // ALERT if (option == "ALERT") { rest.pos = rest.data.find(']', rest.pos) + 1; // The alert text is after [ALERT]. // Is this correct or do we need to care about litterals? selectInfo.setAlert( rest.cstr() ); } break; case 'N': // NEWNAME if (option == "NEWNAME") { } break; case 'P': //PARSE or PERMANENTFLAGS if (option == "PARSE") { } else if (option == "PERMANENTFLAGS") { uint end = rest.data.find(']', rest.pos); Q3CString flags(rest.data.data() + rest.pos, end - rest.pos); selectInfo.setPermanentFlags (flags); rest.pos = end; } break; case 'R': //READ-ONLY or READ-WRITE if (option == "READ-ONLY") { selectInfo.setReadWrite (false); } else if (option == "READ-WRITE") { selectInfo.setReadWrite (true); } break; case 'T': //TRYCREATE if (option == "TRYCREATE") { } break; case 'U': //UIDVALIDITY or UNSEEN if (option == "UIDVALIDITY") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUidValidity (value); } else if (option == "UNSEEN") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUnseen (value); } else if (option == "UIDNEXT") { ulong value; if (parseOneNumber (rest, value)) selectInfo.setUidNext (value); } else break; } if (rest[0] == ']') rest.pos++; //tie off ] skipWS (rest); } if (command.isEmpty()) { // This happens when parsing an intermediate result line (those that start with '*'). // No state change involved, so we can stop here. return; } switch (command[0].latin1 ()) { case 'A': if (command == "AUTHENTICATE") if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_LOGIN; break; case 'L': if (command == "LOGIN") if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_LOGIN; break; case 'E': if (command == "EXAMINE") { if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_SELECT; else { if (currentState == ISTATE_SELECT) currentState = ISTATE_LOGIN; currentBox = QString::null; } kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl; } break; case 'S': if (command == "SELECT") { if (qstrncmp(result, "OK", result.size()) == 0) currentState = ISTATE_SELECT; else { if (currentState == ISTATE_SELECT) currentState = ISTATE_LOGIN; currentBox = QString::null; } kdDebug(7116) << "imapParser::parseResult - current box is now " << currentBox << endl; } break; default: break; } } void imapParser::parseCapability (parseString & result) { Q3CString temp( result.cstr() ); imapCapabilities = QStringList::split ( ' ', KPIM::kAsciiToLower( temp.data() ) ); } void imapParser::parseFlags (parseString & result) { selectInfo.setFlags(result.cstr()); } void imapParser::parseList (parseString & result) { imapList this_one; if (result[0] != '(') return; //not proper format for us result.pos++; // tie off ( this_one.parseAttributes( result ); result.pos++; // tie off ) skipWS (result); this_one.setHierarchyDelimiter(parseLiteralC(result)); this_one.setName (rfcDecoder::fromIMAP(parseLiteralC(result))); // decode modified UTF7 listResponses.append (this_one); } void imapParser::parseLsub (parseString & result) { imapList this_one (result.cstr()); listResponses.append (this_one); } void imapParser::parseListRights (parseString & result) { parseOneWordC (result); // skip mailbox name parseOneWordC (result); // skip user id int outlen = 1; while ( outlen ) { Q3CString word = parseOneWordC (result, false, &outlen); lastResults.append (word); } } void imapParser::parseAcl (parseString & result) { parseOneWordC (result); // skip mailbox name int outlen = 1; // The result is user1 perm1 user2 perm2 etc. The caller will sort it out. while ( outlen && !result.isEmpty() ) { Q3CString word = parseLiteralC (result, false, false, &outlen); lastResults.append (word); } } void imapParser::parseAnnotation (parseString & result) { parseOneWordC (result); // skip mailbox name skipWS (result); parseOneWordC (result); // skip entry name (we know it since we don't allow wildcards in it) skipWS (result); if (result.isEmpty() || result[0] != '(') return; result.pos++; skipWS (result); int outlen = 1; // The result is name1 value1 name2 value2 etc. The caller will sort it out. while ( outlen && !result.isEmpty() && result[0] != ')' ) { Q3CString word = parseLiteralC (result, false, false, &outlen); lastResults.append (word); } } void imapParser::parseMyRights (parseString & result) { parseOneWordC (result); // skip mailbox name Q_ASSERT( lastResults.isEmpty() ); // we can only be called once lastResults.append (parseOneWordC (result) ); } void imapParser::parseSearch (parseString & result) { ulong value; while (parseOneNumber (result, value)) { lastResults.append (QString::number(value)); } } void imapParser::parseStatus (parseString & inWords) { lastStatus = imapInfo (); parseLiteralC(inWords); // swallow the box if (inWords[0] != '(') return; inWords.pos++; skipWS (inWords); while (!inWords.isEmpty() && inWords[0] != ')') { ulong value; Q3CString label = parseOneWordC(inWords); if (parseOneNumber (inWords, value)) { if (label == "MESSAGES") lastStatus.setCount (value); else if (label == "RECENT") lastStatus.setRecent (value); else if (label == "UIDVALIDITY") lastStatus.setUidValidity (value); else if (label == "UNSEEN") lastStatus.setUnseen (value); else if (label == "UIDNEXT") lastStatus.setUidNext (value); } } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); } void imapParser::parseExists (ulong value, parseString & result) { selectInfo.setCount (value); result.pos = result.data.size(); } void imapParser::parseExpunge (ulong value, parseString & result) { Q_UNUSED(value); Q_UNUSED(result); } void imapParser::parseAddressList (parseString & inWords, Q3PtrList& list) { if (inWords[0] != '(') { parseOneWord (inWords); // parse NIL } else { inWords.pos++; skipWS (inWords); while (!inWords.isEmpty () && inWords[0] != ')') { if (inWords[0] == '(') { mailAddress *addr = new mailAddress; parseAddress(inWords, *addr); list.append(addr); } else { break; } } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); } } const mailAddress& imapParser::parseAddress (parseString & inWords, mailAddress& retVal) { inWords.pos++; skipWS (inWords); retVal.setFullName(rfcDecoder::quoteIMAP(parseLiteralC(inWords))); retVal.setCommentRaw(parseLiteralC(inWords)); retVal.setUser(parseLiteralC(inWords)); retVal.setHost(parseLiteralC(inWords)); if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return retVal; } mailHeader * imapParser::parseEnvelope (parseString & inWords) { mailHeader *envelope = 0; if (inWords[0] != '(') return envelope; inWords.pos++; skipWS (inWords); envelope = new mailHeader; //date envelope->setDate(parseLiteralC(inWords)); //subject envelope->setSubject(parseLiteralC(inWords)); Q3PtrList list; list.setAutoDelete(true); //from parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setFrom(*list.last()); list.clear(); } //sender parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setSender(*list.last()); list.clear(); } //reply-to parseAddressList(inWords, list); if (!list.isEmpty()) { envelope->setReplyTo(*list.last()); list.clear(); } //to parseAddressList (inWords, envelope->to()); //cc parseAddressList (inWords, envelope->cc()); //bcc parseAddressList (inWords, envelope->bcc()); //in-reply-to envelope->setInReplyTo(parseLiteralC(inWords)); //message-id envelope->setMessageId(parseLiteralC(inWords)); // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC (inWords); } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return envelope; } // parse parameter pairs into a dictionary // caller must clean up the dictionary items Q3AsciiDict < QString > imapParser::parseDisposition (parseString & inWords) { QByteArray disposition; Q3AsciiDict < QString > retVal (17, false); // return value is a shallow copy retVal.setAutoDelete (false); if (inWords[0] != '(') { //disposition only disposition = parseOneWord (inWords); } else { inWords.pos++; skipWS (inWords); //disposition disposition = parseOneWord (inWords); retVal = parseParameters (inWords); if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } if (!disposition.isEmpty ()) { retVal.insert ("content-disposition", new QString(b2c(disposition))); } return retVal; } // parse parameter pairs into a dictionary // caller must clean up the dictionary items Q3AsciiDict < QString > imapParser::parseParameters (parseString & inWords) { Q3AsciiDict < QString > retVal (17, false); // return value is a shallow copy retVal.setAutoDelete (false); if (inWords[0] != '(') { //better be NIL parseOneWord (inWords); } else { inWords.pos++; skipWS (inWords); while (!inWords.isEmpty () && inWords[0] != ')') { retVal.insert (parseLiteralC(inWords), new QString(parseLiteralC(inWords))); } if (inWords[0] != ')') return retVal; inWords.pos++; skipWS (inWords); } return retVal; } mimeHeader * imapParser::parseSimplePart (parseString & inWords, QString & inSection, mimeHeader * localPart) { Q3CString subtype; Q3CString typeStr; Q3AsciiDict < QString > parameters (17, false); ulong size; parameters.setAutoDelete (true); if (inWords[0] != '(') return 0; if (!localPart) localPart = new mimeHeader; localPart->setPartSpecifier (inSection); inWords.pos++; skipWS (inWords); //body type typeStr = parseLiteralC(inWords); //body subtype subtype = parseLiteralC(inWords); localPart->setType (typeStr + "/" + subtype); //body parameter parenthesized list parameters = parseParameters (inWords); { Q3AsciiDictIterator < QString > it (parameters); while (it.current ()) { localPart->setTypeParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } //body id localPart->setID (parseLiteralC(inWords)); //body description localPart->setDescription (parseLiteralC(inWords)); //body encoding localPart->setEncoding (parseLiteralC(inWords)); //body size if (parseOneNumber (inWords, size)) localPart->setLength (size); // type specific extensions if (localPart->getType().upper() == "MESSAGE/RFC822") { //envelope structure mailHeader *envelope = parseEnvelope (inWords); //body structure parseBodyStructure (inWords, inSection, envelope); localPart->setNestedMessage (envelope); //text lines ulong lines; parseOneNumber (inWords, lines); } else { if (typeStr == "TEXT") { //text lines ulong lines; parseOneNumber (inWords, lines); } // md5 parseLiteralC(inWords); // body disposition parameters = parseDisposition (inWords); { QString *disposition = parameters["content-disposition"]; if (disposition) localPart->setDisposition (disposition->ascii ()); parameters.remove ("content-disposition"); Q3AsciiDictIterator < QString > it (parameters); while (it.current ()) { localPart->setDispositionParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } // body language parseSentence (inWords); } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC(inWords); } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return localPart; } mimeHeader * imapParser::parseBodyStructure (parseString & inWords, QString & inSection, mimeHeader * localPart) { bool init = false; if (inSection.isEmpty()) { // first run init = true; // assume one part inSection = "1"; } int section = 0; if (inWords[0] != '(') { // skip "" parseOneWord (inWords); return 0; } inWords.pos++; skipWS (inWords); if (inWords[0] == '(') { QByteArray subtype; Q3AsciiDict < QString > parameters (17, false); QString outSection; parameters.setAutoDelete (true); if (!localPart) localPart = new mimeHeader; else { // might be filled from an earlier run localPart->clearNestedParts (); localPart->clearTypeParameters (); localPart->clearDispositionParameters (); // an envelope was passed in so this is the multipart header outSection = inSection + ".HEADER"; } if (inWords[0] == '(' && init) inSection = "0"; // set the section if ( !outSection.isEmpty() ) { localPart->setPartSpecifier(outSection); } else { localPart->setPartSpecifier(inSection); } // is multipart (otherwise its a simplepart and handled later) while (inWords[0] == '(') { outSection = QString::number(++section); if (!init) outSection = inSection + "." + outSection; mimeHeader *subpart = parseBodyStructure (inWords, outSection, 0); localPart->addNestedPart (subpart); } // fetch subtype subtype = parseOneWord (inWords); localPart->setType ("MULTIPART/" + b2c(subtype)); // fetch parameters parameters = parseParameters (inWords); { Q3AsciiDictIterator < QString > it (parameters); while (it.current ()) { localPart->setTypeParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } // body disposition parameters = parseDisposition (inWords); { QString *disposition = parameters["content-disposition"]; if (disposition) localPart->setDisposition (disposition->ascii ()); parameters.remove ("content-disposition"); Q3AsciiDictIterator < QString > it (parameters); while (it.current ()) { localPart->setDispositionParm (it.currentKey (), *(it.current ())); ++it; } parameters.clear (); } // body language parseSentence (inWords); } else { // is simple part inWords.pos--; inWords.data[inWords.pos] = '('; //fake a sentence if ( localPart ) inSection = inSection + ".1"; localPart = parseSimplePart (inWords, inSection, localPart); inWords.pos--; inWords.data[inWords.pos] = ')'; //remove fake } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC(inWords); } if (inWords[0] == ')') inWords.pos++; skipWS (inWords); return localPart; } void imapParser::parseBody (parseString & inWords) { // see if we got a part specifier if (inWords[0] == '[') { QByteArray specifier; QByteArray label; inWords.pos++; specifier = parseOneWord (inWords, TRUE); if (inWords[0] == '(') { inWords.pos++; while (!inWords.isEmpty () && inWords[0] != ')') { label = parseOneWord (inWords); } if (inWords[0] == ')') inWords.pos++; } if (inWords[0] == ']') inWords.pos++; skipWS (inWords); // parse the header if (qstrncmp(specifier, "0", specifier.size()) == 0) { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; // don't know where to put it, throw it away parseLiteralC(inWords, true); } else { kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl; // fill it up with data QString theHeader = parseLiteralC(inWords, true); mimeIOQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); } } else if (qstrncmp(specifier, "HEADER.FIELDS", specifier.size()) == 0) { // BODY[HEADER.FIELDS (References)] {n} //kdDebug(7116) << "imapParser::parseBody - HEADER.FIELDS: " // << QCString(label.data(), label.size()+1) << endl; if (qstrncmp(label, "REFERENCES", label.size()) == 0) { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; // don't know where to put it, throw it away parseLiteralC (inWords, true); } else { Q3CString references = parseLiteralC(inWords, true); int start = references.find ('<'); int end = references.findRev ('>'); if (start < end) references = references.mid (start, end - start + 1); envelope->setReferences(references.simplifyWhiteSpace()); } } else { // not a header we care about throw it away parseLiteralC(inWords, true); } } else { Q3CString spec(specifier.data(), specifier.size()+1); if (spec.find(".MIME") != -1) { mailHeader *envelope = new mailHeader; QString theHeader = parseLiteralC(inWords, false); mimeIOQString myIO; myIO.setString (theHeader); envelope->parseHeader (myIO); if (lastHandled) lastHandled->setHeader (envelope); return; } // throw it away kdDebug(7116) << "imapParser::parseBody - discarding " << seenUid.ascii () << endl; parseLiteralC(inWords, true); } } else // no part specifier { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); if (!envelope || seenUid.isEmpty ()) { kdDebug(7116) << "imapParser::parseBody - discarding " << envelope << " " << seenUid.ascii () << endl; // don't know where to put it, throw it away parseSentence (inWords); } else { kdDebug(7116) << "imapParser::parseBody - reading " << envelope << " " << seenUid.ascii () << endl; // fill it up with data QString section; mimeHeader *body = parseBodyStructure (inWords, section, envelope); if (body != envelope) delete body; } } } void imapParser::parseFetch (ulong /* value */, parseString & inWords) { if (inWords[0] != '(') return; inWords.pos++; skipWS (inWords); delete lastHandled; lastHandled = 0; while (!inWords.isEmpty () && inWords[0] != ')') { if (inWords[0] == '(') parseSentence (inWords); else { Q3CString word = parseLiteralC(inWords, false, true); switch (word[0]) { case 'E': if (word == "ENVELOPE") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); else lastHandled = new imapCache(); if (envelope && !envelope->getMessageId ().isEmpty ()) { // we have seen this one already // or don't know where to put it parseSentence (inWords); } else { envelope = parseEnvelope (inWords); if (envelope) { envelope->setPartSpecifier (seenUid + ".0"); lastHandled->setHeader (envelope); lastHandled->setUid (seenUid.toULong ()); } } } break; case 'B': if (word == "BODY") { parseBody (inWords); } else if (word == "BODY[]" ) { // Do the same as with "RFC822" parseLiteralC(inWords, true); } else if (word == "BODYSTRUCTURE") { mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); // fill it up with data QString section; mimeHeader *body = parseBodyStructure (inWords, section, envelope); QByteArray data; QDataStream stream( &data, QIODevice::WriteOnly ); body->serialize(stream); parseRelay(data); delete body; } break; case 'U': if (word == "UID") { seenUid = parseOneWordC(inWords); mailHeader *envelope = 0; if (lastHandled) envelope = lastHandled->getHeader (); else lastHandled = new imapCache(); if (seenUid.isEmpty ()) { // unknown what to do kdDebug(7116) << "imapParser::parseFetch - UID empty" << endl; } else { lastHandled->setUid (seenUid.toULong ()); } if (envelope) envelope->setPartSpecifier (seenUid); } break; case 'R': if (word == "RFC822.SIZE") { ulong size; parseOneNumber (inWords, size); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setSize (size); } else if (word.find ("RFC822") == 0) { // might be RFC822 RFC822.TEXT RFC822.HEADER parseLiteralC(inWords, true); } break; case 'I': if (word == "INTERNALDATE") { Q3CString date = parseOneWordC(inWords); if (!lastHandled) lastHandled = new imapCache(); lastHandled->setDate(date); } break; case 'F': if (word == "FLAGS") { //kdDebug(7116) << "GOT FLAGS " << inWords.cstr() << endl; if (!lastHandled) lastHandled = new imapCache(); lastHandled->setFlags (imapInfo::_flags (inWords.cstr())); } break; default: parseLiteralC(inWords); break; } } } // see if we have more to come while (!inWords.isEmpty () && inWords[0] != ')') { //eat the extensions to this part if (inWords[0] == '(') parseSentence (inWords); else parseLiteralC(inWords); } if (inWords[0] != ')') return; inWords.pos++; skipWS (inWords); } // default parser void imapParser::parseSentence (parseString & inWords) { bool first = true; int stack = 0; //find the first nesting parentheses while (!inWords.isEmpty () && (stack != 0 || first)) { first = false; skipWS (inWords); unsigned char ch = inWords[0]; switch (ch) { case '(': inWords.pos++; ++stack; break; case ')': inWords.pos++; --stack; break; case '[': inWords.pos++; ++stack; break; case ']': inWords.pos++; --stack; break; default: parseLiteralC(inWords); skipWS (inWords); break; } } skipWS (inWords); } void imapParser::parseRecent (ulong value, parseString & result) { selectInfo.setRecent (value); result.pos = result.data.size(); } void imapParser::parseNamespace (parseString & result) { if ( result[0] != '(' ) return; QString delimEmpty; if ( namespaceToDelimiter.contains( QString::null ) ) delimEmpty = namespaceToDelimiter[QString::null]; namespaceToDelimiter.clear(); imapNamespaces.clear(); // remember what section we're in (user, other users, shared) int ns = -1; bool personalAvailable = false; while ( !result.isEmpty() ) { if ( result[0] == '(' ) { result.pos++; // tie off ( if ( result[0] == '(' ) { // new namespace section result.pos++; // tie off ( ++ns; } // namespace prefix Q3CString prefix = parseOneWordC( result ); // delimiter Q3CString delim = parseOneWordC( result ); kdDebug(7116) << "imapParser::parseNamespace ns='" << prefix << "',delim='" << delim << "'" << endl; if ( ns == 0 ) { // at least one personal ns personalAvailable = true; } QString nsentry = QString::number( ns ) + "=" + QString(prefix) + "=" + QString(delim); imapNamespaces.append( nsentry ); if ( prefix.right( 1 ) == delim ) { // strip delimiter to get a correct entry for comparisons prefix.resize( prefix.length() ); } namespaceToDelimiter[prefix] = delim; result.pos++; // tie off ) skipWS( result ); } else if ( result[0] == ')' ) { result.pos++; // tie off ) skipWS( result ); } else if ( result[0] == 'N' ) { // drop NIL ++ns; parseOneWord( result ); } else { // drop whatever it is parseOneWord( result ); } } if ( !delimEmpty.isEmpty() ) { // remember default delimiter namespaceToDelimiter[QString::null] = delimEmpty; if ( !personalAvailable ) { // at least one personal ns would be nice kdDebug(7116) << "imapParser::parseNamespace - registering own personal ns" << endl; QString nsentry = "0==" + delimEmpty; imapNamespaces.append( nsentry ); } } } int imapParser::parseLoop () { parseString result; if (!parseReadLine(result.data)) return -1; //kdDebug(7116) << result.cstr(); // includes \n if (result.data.isEmpty()) return 0; if (!sentQueue.count ()) { // maybe greeting or BYE everything else SHOULD not happen, use NOOP or IDLE kdDebug(7116) << "imapParser::parseLoop - unhandledResponse: \n" << result.cstr() << endl; unhandled << result.cstr(); } else { imapCommand *current = sentQueue.at (0); switch (result[0]) { case '*': result.data.resize(result.data.size() - 2); // tie off CRLF parseUntagged (result); break; case '+': continuation.duplicate(result.data); break; default: { Q3CString tag = parseLiteralC(result); if (current->id() == tag.data()) { result.data.resize(result.data.size() - 2); // tie off CRLF QByteArray resultCode = parseLiteral (result); //the result current->setResult (resultCode); current->setResultInfo(result.cstr()); current->setComplete (); sentQueue.removeRef (current); completeQueue.append (current); if (result.length()) parseResult (resultCode, result, current->command()); } else { kdDebug(7116) << "imapParser::parseLoop - unknown tag '" << tag << "'" << endl; Q3CString cstr = tag + " " + result.cstr(); result.data = cstr; result.pos = 0; result.data.resize(cstr.length()); } } break; } } return 1; } void imapParser::parseRelay (const QByteArray & buffer) { Q_UNUSED(buffer); qWarning ("imapParser::parseRelay - virtual function not reimplemented - data lost"); } void imapParser::parseRelay (ulong len) { Q_UNUSED(len); qWarning ("imapParser::parseRelay - virtual function not reimplemented - announcement lost"); } bool imapParser::parseRead (QByteArray & buffer, long len, long relay) { Q_UNUSED(buffer); Q_UNUSED(len); Q_UNUSED(relay); qWarning ("imapParser::parseRead - virtual function not reimplemented - no data read"); return FALSE; } bool imapParser::parseReadLine (QByteArray & buffer, long relay) { Q_UNUSED(buffer); Q_UNUSED(relay); qWarning ("imapParser::parseReadLine - virtual function not reimplemented - no data read"); return FALSE; } void imapParser::parseWriteLine (const QString & str) { Q_UNUSED(str); qWarning ("imapParser::parseWriteLine - virtual function not reimplemented - no data written"); } void imapParser::parseURL (const KURL & _url, QString & _box, QString & _section, QString & _type, QString & _uid, QString & _validity, QString & _info) { QStringList parameters; _box = _url.path (); kdDebug(7116) << "imapParser::parseURL " << _box << endl; int paramStart = _box.find("/;"); if ( paramStart > -1 ) { QString paramString = _box.right( _box.length() - paramStart-2 ); parameters = QStringList::split (';', paramString); //split parameters _box.truncate( paramStart ); // strip parameters } // extract parameters for (QStringList::ConstIterator it (parameters.begin ()); it != parameters.end (); ++it) { QString temp = (*it); // if we have a '/' separator we'll just nuke it int pt = temp.find ('/'); if (pt > 0) temp.truncate(pt); if (temp.find ("section=", 0, false) == 0) _section = temp.right (temp.length () - 8); else if (temp.find ("type=", 0, false) == 0) _type = temp.right (temp.length () - 5); else if (temp.find ("uid=", 0, false) == 0) _uid = temp.right (temp.length () - 4); else if (temp.find ("uidvalidity=", 0, false) == 0) _validity = temp.right (temp.length () - 12); else if (temp.find ("info=", 0, false) == 0) _info = temp.right (temp.length () - 5); } // kdDebug(7116) << "URL: section= " << _section << ", type= " << _type << ", uid= " << _uid << endl; // kdDebug(7116) << "URL: user() " << _url.user() << endl; // kdDebug(7116) << "URL: path() " << _url.path() << endl; // kdDebug(7116) << "URL: encodedPathAndQuery() " << _url.encodedPathAndQuery() << endl; if (!_box.isEmpty ()) { // strip / if (_box[0] == '/') _box = _box.right (_box.length () - 1); if (!_box.isEmpty () && _box[_box.length () - 1] == '/') _box.truncate(_box.length() - 1); } kdDebug(7116) << "URL: box= " << _box << ", section= " << _section << ", type= " << _type << ", uid= " << _uid << ", validity= " << _validity << ", info= " << _info << endl; } Q3CString imapParser::parseLiteralC(parseString & inWords, bool relay, bool stopAtBracket, int *outlen) { if (inWords[0] == '{') { Q3CString retVal; int runLen = inWords.find ('}', 1); if (runLen > 0) { bool proper; long runLenSave = runLen + 1; Q3CString tmpstr(runLen); inWords.takeMidNoResize(tmpstr, 1, runLen - 1); runLen = tmpstr.toULong (&proper); inWords.pos += runLenSave; if (proper) { //now get the literal from the server if (relay) parseRelay (runLen); QByteArray rv; parseRead (rv, runLen, relay ? runLen : 0); - rv.resize(QMAX(runLen, rv.size())); // what's the point? + rv.resize(qMax(runLen, rv.size())); // what's the point? retVal = b2c(rv); inWords.clear(); parseReadLine (inWords.data); // must get more // no duplicate data transfers relay = false; } else { kdDebug(7116) << "imapParser::parseLiteral - error parsing {} - " /*<< strLen*/ << endl; } } else { inWords.clear(); kdDebug(7116) << "imapParser::parseLiteral - error parsing unmatched {" << endl; } if (outlen) { *outlen = retVal.length(); // optimize me } skipWS (inWords); return retVal; } return parseOneWordC(inWords, stopAtBracket, outlen); } // does not know about literals ( {7} literal ) Q3CString imapParser::parseOneWordC (parseString & inWords, bool stopAtBracket, int *outLen) { uint retValSize = 0; uint len = inWords.length(); if (len == 0) { return Q3CString(); } if (len > 0 && inWords[0] == '"') { unsigned int i = 1; bool quote = FALSE; while (i < len && (inWords[i] != '"' || quote)) { if (inWords[i] == '\\') quote = !quote; else quote = FALSE; i++; } if (i < len) { Q3CString retVal(i); inWords.pos++; inWords.takeLeftNoResize(retVal, i - 1); len = i - 1; int offset = 0; for (unsigned int j = 0; j <= len; j++) { if (retVal[j] == '\\') { offset++; j++; } retVal[j - offset] = retVal[j]; } retVal[len - offset] = 0; retValSize = len - offset; inWords.pos += i; skipWS (inWords); if (outLen) { *outLen = retValSize; } return retVal; } else { kdDebug(7116) << "imapParser::parseOneWord - error parsing unmatched \"" << endl; Q3CString retVal = inWords.cstr(); retValSize = len; inWords.clear(); if (outLen) { *outLen = retValSize; } return retVal; } } else { // not quoted unsigned int i; // search for end for (i = 0; i < len; ++i) { char ch = inWords[i]; if (ch <= ' ' || ch == '(' || ch == ')' || (stopAtBracket && (ch == '[' || ch == ']'))) break; } Q3CString retVal(i+1); inWords.takeLeftNoResize(retVal, i); retValSize = i; inWords.pos += i; if (retVal == "NIL") { retVal.truncate(0); retValSize = 0; } skipWS (inWords); if (outLen) { *outLen = retValSize; } return retVal; } } bool imapParser::parseOneNumber (parseString & inWords, ulong & num) { bool valid; num = parseOneWordC(inWords, TRUE).toULong(&valid); return valid; } bool imapParser::hasCapability (const QString & cap) { QString c = cap.lower(); // kdDebug(7116) << "imapParser::hasCapability - Looking for '" << cap << "'" << endl; for (QStringList::ConstIterator it = imapCapabilities.begin (); it != imapCapabilities.end (); ++it) { // kdDebug(7116) << "imapParser::hasCapability - Examining '" << (*it) << "'" << endl; if ( !(kasciistricmp(c.ascii(), (*it).ascii())) ) { return true; } } return false; } void imapParser::removeCapability (const QString & cap) { imapCapabilities.remove(cap.lower()); } QString imapParser::namespaceForBox( const QString & box ) { kdDebug(7116) << "imapParse::namespaceForBox " << box << endl; QString myNamespace; if ( !box.isEmpty() ) { Q3ValueList list = namespaceToDelimiter.keys(); QString cleanPrefix; for ( Q3ValueList::Iterator it = list.begin(); it != list.end(); ++it ) { if ( !(*it).isEmpty() && box.find( *it ) != -1 ) return (*it); } } return myNamespace; } diff --git a/kpimidentities/identity.cpp b/kpimidentities/identity.cpp index 8e5d089ea..4de54ac35 100644 --- a/kpimidentities/identity.cpp +++ b/kpimidentities/identity.cpp @@ -1,617 +1,617 @@ // -*- mode: C++; c-file-style: "gnu" -*- // kmidentity.cpp // License: GPL #ifdef HAVE_CONFIG_H #include #endif #include "identity.h" #include #include #include #include #include #include #include #include //Added by qt3to4: #include #include #include #include #include #include using namespace KPIM; Signature::Signature() : mType( Disabled ) { } Signature::Signature( const QString & text ) : mText( text ), mType( Inlined ) { } Signature::Signature( const QString & url, bool isExecutable ) : mUrl( url ), mType( isExecutable ? FromCommand : FromFile ) { } bool Signature::operator==( const Signature & other ) const { if ( mType != other.mType ) return false; switch ( mType ) { case Inlined: return mText == other.mText; case FromFile: case FromCommand: return mUrl == other.mUrl; default: case Disabled: return true; } } QString Signature::rawText( bool * ok ) const { switch ( mType ) { case Disabled: if ( ok ) *ok = true; return QString::null; case Inlined: if ( ok ) *ok = true; return mText; case FromFile: return textFromFile( ok ); case FromCommand: return textFromCommand( ok ); }; kdFatal( 5006 ) << "Signature::type() returned unknown value!" << endl; return QString::null; // make compiler happy } QString Signature::textFromCommand( bool * ok ) const { assert( mType == FromCommand ); // handle pathological cases: if ( mUrl.isEmpty() ) { if ( ok ) *ok = true; return QString::null; } // create a shell process: CollectingProcess proc; proc.setUseShell(true); proc << mUrl; // run the process: int rc = 0; if ( !proc.start( KProcess::Block, KProcess::Stdout ) ) rc = -1; else rc = ( proc.normalExit() ) ? proc.exitStatus() : -1 ; // handle errors, if any: if ( rc != 0 ) { if ( ok ) *ok = false; QString wmsg = i18n("Failed to execute signature script
    %1:
    %2
    ") .arg( mUrl ).arg( strerror(rc) ); KMessageBox::error(0, wmsg); return QString::null; } // no errors: if ( ok ) *ok = true; // get output: QByteArray output = proc.collectedStdout(); // ### hmm, should we allow other encodings, too? return QString::fromLocal8Bit( output.data(), output.size() ); } QString Signature::textFromFile( bool * ok ) const { assert( mType == FromFile ); // ### FIXME: Use KIO::NetAccess to download non-local files! if ( !KURL(mUrl).isLocalFile() && !(QFileInfo(mUrl).isRelative() && QFileInfo(mUrl).exists()) ) { kdDebug( 5006 ) << "Signature::textFromFile: non-local URLs are unsupported" << endl; if ( ok ) *ok = false; return QString::null; } if ( ok ) *ok = true; // ### hmm, should we allow other encodings, too? const QByteArray ba = kFileToByteArray( mUrl, false ); return QString::fromLocal8Bit( ba.data(), ba.size() ); } QString Signature::withSeparator( bool * ok ) const { bool internalOK = false; QString signature = rawText( &internalOK ); if ( !internalOK ) { if ( ok ) *ok = false; return QString::null; } if ( ok ) *ok = true; if ( signature.isEmpty() ) return signature; // don't add a separator in this case if ( signature.startsWith( QString::fromLatin1("-- \n") ) ) // already have signature separator at start of sig: return QString::fromLatin1("\n") += signature; else if ( signature.find( QString::fromLatin1("\n-- \n") ) != -1 ) // already have signature separator inside sig; don't prepend '\n' // to improve abusing signatures as templates: return signature; else // need to prepend one: return QString::fromLatin1("\n-- \n") + signature; } void Signature::setUrl( const QString & url, bool isExecutable ) { mUrl = url; mType = isExecutable ? FromCommand : FromFile ; } // config keys and values: static const char sigTypeKey[] = "Signature Type"; static const char sigTypeInlineValue[] = "inline"; static const char sigTypeFileValue[] = "file"; static const char sigTypeCommandValue[] = "command"; static const char sigTypeDisabledValue[] = "disabled"; static const char sigTextKey[] = "Inline Signature"; static const char sigFileKey[] = "Signature File"; static const char sigCommandKey[] = "Signature Command"; void Signature::readConfig( const KConfigBase * config ) { QString sigType = config->readEntry( sigTypeKey ); if ( sigType == sigTypeInlineValue ) { mType = Inlined; mText = config->readEntry( sigTextKey ); } else if ( sigType == sigTypeFileValue ) { mType = FromFile; mUrl = config->readPathEntry( sigFileKey ); } else if ( sigType == sigTypeCommandValue ) { mType = FromCommand; mUrl = config->readPathEntry( sigCommandKey ); } else { mType = Disabled; } } void Signature::writeConfig( KConfigBase * config ) const { switch ( mType ) { case Inlined: config->writeEntry( sigTypeKey, sigTypeInlineValue ); config->writeEntry( sigTextKey, mText ); break; case FromFile: config->writeEntry( sigTypeKey, sigTypeFileValue ); config->writePathEntry( sigFileKey, mUrl ); break; case FromCommand: config->writeEntry( sigTypeKey, sigTypeCommandValue ); config->writePathEntry( sigCommandKey, mUrl ); break; case Disabled: config->writeEntry( sigTypeKey, sigTypeDisabledValue ); default: ; } } QDataStream & KPIM::operator<<( QDataStream & stream, const KPIM::Signature & sig ) { - return stream << static_cast(sig.mType) + return stream << static_cast(sig.mType) << sig.mUrl << sig.mText; } QDataStream & KPIM::operator>>( QDataStream & stream, KPIM::Signature & sig ) { - Q_UINT8 s; + quint8 s; stream >> s >> sig.mUrl >> sig.mText; sig.mType = static_cast(s); return stream; } // ### should use a kstaticdeleter? const Identity Identity::null; bool Identity::isNull() const { return mIdentity.isEmpty() && mFullName.isEmpty() && mEmailAddr.isEmpty() && mOrganization.isEmpty() && mReplyToAddr.isEmpty() && mBcc.isEmpty() && mVCardFile.isEmpty() && mFcc.isEmpty() && mDrafts.isEmpty() && mPGPEncryptionKey.isEmpty() && mPGPSigningKey.isEmpty() && mSMIMEEncryptionKey.isEmpty() && mSMIMESigningKey.isEmpty() && mTransport.isEmpty() && mDictionary.isEmpty() && mPreferredCryptoMessageFormat == Kleo::AutoFormat && mSignature.type() == Signature::Disabled && mXFace.isEmpty(); } bool Identity::operator==( const Identity & other ) const { bool same = mUoid == other.mUoid && mIdentity == other.mIdentity && mFullName == other.mFullName && mEmailAddr == other.mEmailAddr && mOrganization == other.mOrganization && mReplyToAddr == other.mReplyToAddr && mBcc == other.mBcc && mVCardFile == other.mVCardFile && mFcc == other.mFcc && mPGPEncryptionKey == other.mPGPEncryptionKey && mPGPSigningKey == other.mPGPSigningKey && mSMIMEEncryptionKey == other.mSMIMEEncryptionKey && mSMIMESigningKey == other.mSMIMESigningKey && mPreferredCryptoMessageFormat == other.mPreferredCryptoMessageFormat && mDrafts == other.mDrafts && mTransport == other.mTransport && mDictionary == other.mDictionary && mSignature == other.mSignature && mXFace == other.mXFace && mXFaceEnabled == other.mXFaceEnabled; #if 0 if ( same ) return true; if ( mUoid != other.mUoid ) kdDebug() << "mUoid differs : " << mUoid << " != " << other.mUoid << endl; if ( mIdentity != other.mIdentity ) kdDebug() << "mIdentity differs : " << mIdentity << " != " << other.mIdentity << endl; if ( mFullName != other.mFullName ) kdDebug() << "mFullName differs : " << mFullName << " != " << other.mFullName << endl; if ( mEmailAddr != other.mEmailAddr ) kdDebug() << "mEmailAddr differs : " << mEmailAddr << " != " << other.mEmailAddr << endl; if ( mOrganization != other.mOrganization ) kdDebug() << "mOrganization differs : " << mOrganization << " != " << other.mOrganization << endl; if ( mReplyToAddr != other.mReplyToAddr ) kdDebug() << "mReplyToAddr differs : " << mReplyToAddr << " != " << other.mReplyToAddr << endl; if ( mBcc != other.mBcc ) kdDebug() << "mBcc differs : " << mBcc << " != " << other.mBcc << endl; if ( mVCardFile != other.mVCardFile ) kdDebug() << "mVCardFile differs : " << mVCardFile << " != " << other.mVCardFile << endl; if ( mFcc != other.mFcc ) kdDebug() << "mFcc differs : " << mFcc << " != " << other.mFcc << endl; if ( mPGPEncryptionKey != other.mPGPEncryptionKey ) kdDebug() << "mPGPEncryptionKey differs : " << mPGPEncryptionKey << " != " << other.mPGPEncryptionKey << endl; if ( mPGPSigningKey != other.mPGPSigningKey ) kdDebug() << "mPGPSigningKey differs : " << mPGPSigningKey << " != " << other.mPGPSigningKey << endl; if ( mSMIMEEncryptionKey != other.mSMIMEEncryptionKey ) kdDebug() << "mSMIMEEncryptionKey differs : '" << mSMIMEEncryptionKey << "' != '" << other.mSMIMEEncryptionKey << "'" << endl; if ( mSMIMESigningKey != other.mSMIMESigningKey ) kdDebug() << "mSMIMESigningKey differs : " << mSMIMESigningKey << " != " << other.mSMIMESigningKey << endl; if ( mPreferredCryptoMessageFormat != other.mPreferredCryptoMessageFormat ) kdDebug() << "mPreferredCryptoMessageFormat differs : " << mPreferredCryptoMessageFormat << " != " << other.mPreferredCryptoMessageFormat << endl; if ( mDrafts != other.mDrafts ) kdDebug() << "mDrafts differs : " << mDrafts << " != " << other.mDrafts << endl; if ( mTransport != other.mTransport ) kdDebug() << "mTransport differs : " << mTransport << " != " << other.mTransport << endl; if ( mDictionary != other.mDictionary ) kdDebug() << "mDictionary differs : " << mDictionary << " != " << other.mDictionary << endl; if ( ! ( mSignature == other.mSignature ) ) kdDebug() << "mSignature differs" << endl; #endif return same; } Identity::Identity( const QString & id, const QString & fullName, const QString & emailAddr, const QString & organization, const QString & replyToAddr ) : mUoid( 0 ), mIdentity( id ), mFullName( fullName ), mEmailAddr( emailAddr ), mOrganization( organization ), mReplyToAddr( replyToAddr ), // Using "" instead of null to make operator==() not fail // (readConfig returns "") mBcc( "" ), mVCardFile( "" ), mPGPEncryptionKey( "" ), mPGPSigningKey( "" ), mSMIMEEncryptionKey( "" ), mSMIMESigningKey( "" ), mFcc( "" ), mDrafts( "" ), mTransport( "" ), mDictionary( "" ), mXFace( "" ), mXFaceEnabled( false ), mIsDefault( false ), mPreferredCryptoMessageFormat( Kleo::AutoFormat ) { } Identity::~Identity() { } void Identity::readConfig( const KConfigBase * config ) { mUoid = config->readUnsignedNumEntry("uoid",0); mIdentity = config->readEntry("Identity"); mFullName = config->readEntry("Name"); mEmailAddr = config->readEntry("Email Address"); mVCardFile = config->readPathEntry("VCardFile"); mOrganization = config->readEntry("Organization"); mPGPSigningKey = config->readEntry("PGP Signing Key").latin1(); mPGPEncryptionKey = config->readEntry("PGP Encryption Key").latin1(); mSMIMESigningKey = config->readEntry("SMIME Signing Key").latin1(); mSMIMEEncryptionKey = config->readEntry("SMIME Encryption Key").latin1(); mPreferredCryptoMessageFormat = Kleo::stringToCryptoMessageFormat( config->readEntry("Preferred Crypto Message Format", "none" ) ); mReplyToAddr = config->readEntry("Reply-To Address"); mBcc = config->readEntry("Bcc"); mFcc = config->readEntry("Fcc", "sent-mail"); if( mFcc.isEmpty() ) mFcc = "sent-mail"; mDrafts = config->readEntry("Drafts", "drafts"); if( mDrafts.isEmpty() ) mDrafts = "drafts"; mTransport = config->readEntry("Transport"); mDictionary = config->readEntry( "Dictionary" ); mXFace = config->readEntry( "X-Face" ); mXFaceEnabled = config->readBoolEntry( "X-FaceEnabled", false ); mSignature.readConfig( config ); kdDebug(5006) << "Identity::readConfig(): UOID = " << mUoid << " for identity named \"" << mIdentity << "\"" << endl; } void Identity::writeConfig( KConfigBase * config ) const { config->writeEntry("uoid", mUoid); config->writeEntry("Identity", mIdentity); config->writeEntry("Name", mFullName); config->writeEntry("Organization", mOrganization); config->writeEntry("PGP Signing Key", mPGPSigningKey.data()); config->writeEntry("PGP Encryption Key", mPGPEncryptionKey.data()); config->writeEntry("SMIME Signing Key", mSMIMESigningKey.data()); config->writeEntry("SMIME Encryption Key", mSMIMEEncryptionKey.data()); config->writeEntry("Preferred Crypto Message Format", Kleo::cryptoMessageFormatToString( mPreferredCryptoMessageFormat ) ); config->writeEntry("Email Address", mEmailAddr); config->writeEntry("Reply-To Address", mReplyToAddr); config->writeEntry("Bcc", mBcc); config->writePathEntry("VCardFile", mVCardFile); config->writeEntry("Transport", mTransport); config->writeEntry("Fcc", mFcc); config->writeEntry("Drafts", mDrafts); config->writeEntry( "Dictionary", mDictionary ); config->writeEntry( "X-Face", mXFace ); config->writeEntry( "X-FaceEnabled", mXFaceEnabled ); mSignature.writeConfig( config ); } QDataStream & KPIM::operator<<( QDataStream & stream, const KPIM::Identity & i ) { - return stream << static_cast(i.uoid()) + return stream << static_cast(i.uoid()) << i.identityName() << i.fullName() << i.organization() << i.pgpSigningKey() << i.pgpEncryptionKey() << i.smimeSigningKey() << i.smimeEncryptionKey() << i.emailAddr() << i.replyToAddr() << i.bcc() << i.vCardFile() << i.transport() << i.fcc() << i.drafts() << i.mSignature << i.dictionary() << i.xface() << QString( Kleo::cryptoMessageFormatToString( i.mPreferredCryptoMessageFormat ) ); } QDataStream & KPIM::operator>>( QDataStream & stream, KPIM::Identity & i ) { - Q_UINT32 uoid; + quint32 uoid; QString format; stream >> uoid >> i.mIdentity >> i.mFullName >> i.mOrganization >> i.mPGPSigningKey >> i.mPGPEncryptionKey >> i.mSMIMESigningKey >> i.mSMIMEEncryptionKey >> i.mEmailAddr >> i.mReplyToAddr >> i.mBcc >> i.mVCardFile >> i.mTransport >> i.mFcc >> i.mDrafts >> i.mSignature >> i.mDictionary >> i.mXFace >> format; i.mUoid = uoid; i.mPreferredCryptoMessageFormat = Kleo::stringToCryptoMessageFormat( format.latin1() ); return stream; } //----------------------------------------------------------------------------- bool Identity::mailingAllowed() const { return !mEmailAddr.isEmpty(); } void Identity::setIsDefault( bool flag ) { mIsDefault = flag; } void Identity::setIdentityName( const QString & name ) { mIdentity = name; } void Identity::setFullName(const QString &str) { mFullName = str; } //----------------------------------------------------------------------------- void Identity::setOrganization(const QString &str) { mOrganization = str; } void Identity::setPGPSigningKey(const Q3CString &str) { mPGPSigningKey = str; if ( mPGPSigningKey.isNull() ) mPGPSigningKey = ""; } void Identity::setPGPEncryptionKey(const Q3CString &str) { mPGPEncryptionKey = str; if ( mPGPEncryptionKey.isNull() ) mPGPEncryptionKey = ""; } void Identity::setSMIMESigningKey(const Q3CString &str) { mSMIMESigningKey = str; if ( mSMIMESigningKey.isNull() ) mSMIMESigningKey = ""; } void Identity::setSMIMEEncryptionKey(const Q3CString &str) { mSMIMEEncryptionKey = str; if ( mSMIMEEncryptionKey.isNull() ) mSMIMEEncryptionKey = ""; } //----------------------------------------------------------------------------- void Identity::setEmailAddr(const QString &str) { mEmailAddr = str; } //----------------------------------------------------------------------------- void Identity::setVCardFile(const QString &str) { mVCardFile = str; } //----------------------------------------------------------------------------- QString Identity::fullEmailAddr(void) const { if (mFullName.isEmpty()) return mEmailAddr; const QString specials("()<>@,.;:[]"); QString result; // add DQUOTE's if necessary: bool needsQuotes=false; for (int i=0; i < mFullName.length(); i++) { if ( specials.contains( mFullName[i] ) ) needsQuotes = true; else if ( mFullName[i] == '\\' || mFullName[i] == '"' ) { needsQuotes = true; result += '\\'; } result += mFullName[i]; } if (needsQuotes) { result.insert(0,'"'); result += '"'; } result += " <" + mEmailAddr + '>'; return result; } //----------------------------------------------------------------------------- void Identity::setReplyToAddr(const QString& str) { mReplyToAddr = str; } //----------------------------------------------------------------------------- void Identity::setSignatureFile(const QString &str) { mSignature.setUrl( str, signatureIsCommand() ); } //----------------------------------------------------------------------------- void Identity::setSignatureInlineText(const QString &str ) { mSignature.setText( str ); } //----------------------------------------------------------------------------- void Identity::setTransport(const QString &str) { mTransport = str; if ( mTransport.isNull() ) mTransport = ""; } //----------------------------------------------------------------------------- void Identity::setFcc(const QString &str) { mFcc = str; if ( mFcc.isNull() ) mFcc = ""; } //----------------------------------------------------------------------------- void Identity::setDrafts(const QString &str) { mDrafts = str; if ( mDrafts.isNull() ) mDrafts = ""; } //----------------------------------------------------------------------------- void Identity::setDictionary( const QString &str ) { mDictionary = str; if ( mDictionary.isNull() ) mDictionary = ""; } //----------------------------------------------------------------------------- void Identity::setXFace( const QString &str ) { mXFace = str; mXFace.remove( " " ); mXFace.remove( "\n" ); mXFace.remove( "\r" ); } //----------------------------------------------------------------------------- void Identity::setXFaceEnabled( const bool on ) { mXFaceEnabled = on; } //----------------------------------------------------------------------------- QString Identity::signatureText( bool * ok ) const { bool internalOK = false; QString signatureText = mSignature.withSeparator( &internalOK ); if ( internalOK ) { if ( ok ) *ok=true; return signatureText; } // OK, here comes the funny part. The call to // Signature::withSeparator() failed, so we should probably fix the // cause: if ( ok ) *ok = false; return QString::null; #if 0 // ### FIXME: error handling if (mSignatureFile.endsWith("|")) { } else { } #endif return QString::null; } diff --git a/ktnef/lib/ktnefparser.cpp b/ktnef/lib/ktnefparser.cpp index e01adf50c..7fd82c1bf 100644 --- a/ktnef/lib/ktnefparser.cpp +++ b/ktnef/lib/ktnefparser.cpp @@ -1,855 +1,855 @@ /* ktnefparser.cpp Copyright (C) 2002 Michael Goffioul This file is part of KTNEF, the KDE TNEF support library/program. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include "ktnef/ktnefparser.h" #include "ktnef/ktnefattach.h" #include "ktnef/ktnefproperty.h" #include "ktnef/ktnefmessage.h" #include #include #include #include #include #include #include #include #ifdef HAVE_INTTYPES_H #include #endif /* HAVE_INTTYPES_H */ #include "ktnef/ktnefdefs.h" typedef struct { - Q_UINT16 type; - Q_UINT16 tag; + quint16 type; + quint16 tag; QVariant value; struct { - Q_UINT32 type; + quint32 type; QVariant value; } name; } MAPI_value; void clearMAPIName( MAPI_value& mapi ); void clearMAPIValue(MAPI_value& mapi, bool clearName = true); QString readMAPIString( QDataStream& stream, bool isUnicode = false, bool align = true, int len = -1 ); -Q_UINT16 readMAPIValue(QDataStream& stream, MAPI_value& mapi); +quint16 readMAPIValue(QDataStream& stream, MAPI_value& mapi); QDateTime readTNEFDate( QDataStream& stream ); QString readTNEFAddress( QDataStream& stream ); -QByteArray readTNEFData( QDataStream& stream, Q_UINT32 len ); -QVariant readTNEFAttribute( QDataStream& stream, Q_UINT16 type, Q_UINT32 len ); -QDateTime formatTime( Q_UINT32 lowB, Q_UINT32 highB ); +QByteArray readTNEFData( QDataStream& stream, quint32 len ); +QVariant readTNEFAttribute( QDataStream& stream, quint16 type, quint32 len ); +QDateTime formatTime( quint32 lowB, quint32 highB ); QString formatRecipient( const QMap& props ); //------------------------------------------------------------------------------------ class KTNEFParser::ParserPrivate { public: ParserPrivate() { defaultdir_ = "/tmp/"; current_ = 0; deleteDevice_ = false; device_ = 0; message_ = new KTNEFMessage; } ~ParserPrivate() { delete message_; } QDataStream stream_; QIODevice *device_; bool deleteDevice_; QString defaultdir_; KTNEFAttach *current_; KTNEFMessage *message_; }; KTNEFParser::KTNEFParser() { d = new ParserPrivate; } KTNEFParser::~KTNEFParser() { deleteDevice(); delete d; } KTNEFMessage* KTNEFParser::message() const { return d->message_; } void KTNEFParser::deleteDevice() { if ( d->deleteDevice_ ) delete d->device_; d->device_ = 0; d->deleteDevice_ = false; } bool KTNEFParser::decodeMessage() { - Q_UINT32 i1, i2, off; - Q_UINT16 u, tag, type; + quint32 i1, i2, off; + quint16 u, tag, type; QVariant value; // read (type+name) d->stream_ >> i1; tag = ( i1 & 0x0000FFFF ); type = ( ( i1 & 0xFFFF0000 ) >> 16 ); // read data length d->stream_ >> i2; // offset after reading the value off = d->device_->at() + i2; switch ( tag ) { case attAIDOWNER: d->stream_ >> value.asUInt(); d->message_->addProperty( 0x0062, MAPI_TYPE_ULONG, value ); kdDebug() << "Message Owner Appointment ID" << " (length=" << i2 << ")" << endl; break; case attREQUESTRES: d->stream_ >> u; d->message_->addProperty( 0x0063, MAPI_TYPE_UINT16, u ); value = ( bool )u; kdDebug() << "Message Request Response" << " (length=" << i2 << ")" << endl; break; case attDATERECD: value = readTNEFDate( d->stream_ ); d->message_->addProperty( 0x0E06, MAPI_TYPE_TIME, value ); kdDebug() << "Message Receive Date" << " (length=" << i2 << ")" << endl; break; case attMSGCLASS: value = readMAPIString( d->stream_, false, false, i2 ); d->message_->addProperty( 0x001A, MAPI_TYPE_STRING8, value ); kdDebug() << "Message Class" << " (length=" << i2 << ")" << endl; break; case attMSGPRIORITY: d->stream_ >> u; d->message_->addProperty( 0x0026, MAPI_TYPE_ULONG, 2-u ); value = u; kdDebug() << "Message Priority" << " (length=" << i2 << ")" << endl; break; case attMAPIPROPS: kdDebug() << "Message MAPI Properties" << " (length=" << i2 << ")" << endl; { int nProps = d->message_->properties().count(); i2 += d->device_->at(); readMAPIProperties( d->message_->properties(), 0 ); d->device_->at( i2 ); kdDebug() << "Properties: " << d->message_->properties().count() << endl; value = QString( "< %1 properties >" ).arg( d->message_->properties().count() - nProps ); } break; case attTNEFVERSION: d->stream_ >> value.asUInt(); kdDebug() << "Message TNEF Version" << " (length=" << i2 << ")" << endl; break; case attFROM: d->message_->addProperty( 0x0024, MAPI_TYPE_STRING8, readTNEFAddress( d->stream_ ) ); d->device_->at( d->device_->at() - i2 ); value = readTNEFData( d->stream_, i2 ); kdDebug() << "Message From" << " (length=" << i2 << ")" << endl; break; case attSUBJECT: value = readMAPIString( d->stream_, false, false, i2 ); d->message_->addProperty( 0x0037, MAPI_TYPE_STRING8, value ); kdDebug() << "Message Subject" << " (length=" << i2 << ")" << endl; break; case attDATESENT: value = readTNEFDate( d->stream_ ); d->message_->addProperty( 0x0039, MAPI_TYPE_TIME, value ); kdDebug() << "Message Date Sent" << " (length=" << i2 << ")" << endl; break; case attMSGSTATUS: { - Q_UINT8 c; - Q_UINT32 flag = 0; + quint8 c; + quint32 flag = 0; d->stream_ >> c; if ( c & fmsRead ) flag |= MSGFLAG_READ; if ( !( c & fmsModified ) ) flag |= MSGFLAG_UNMODIFIED; if ( c & fmsSubmitted ) flag |= MSGFLAG_SUBMIT; if ( c & fmsHasAttach ) flag |= MSGFLAG_HASATTACH; if ( c & fmsLocal ) flag |= MSGFLAG_UNSENT; d->message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag ); value = c; } kdDebug() << "Message Status" << " (length=" << i2 << ")" << endl; break; case attRECIPTABLE: { - Q_UINT32 rows; + quint32 rows; QList recipTable; d->stream_ >> rows; for ( uint i=0; i props; readMAPIProperties( props, 0 ); recipTable << formatRecipient( props ); } d->message_->addProperty( 0x0E12, MAPI_TYPE_STRING8, recipTable ); d->device_->at( d->device_->at() - i2 ); value = readTNEFData( d->stream_, i2 ); } kdDebug() << "Message Recipient Table" << " (length=" << i2 << ")" << endl; break; case attBODY: value = readMAPIString( d->stream_, false, false, i2 ); d->message_->addProperty( 0x1000, MAPI_TYPE_STRING8, value ); kdDebug() << "Message Body" << " (length=" << i2 << ")" << endl; break; case attDATEMODIFIED: value = readTNEFDate( d->stream_ ); d->message_->addProperty( 0x3008, MAPI_TYPE_TIME, value ); kdDebug() << "Message Date Modified" << " (length=" << i2 << ")" << endl; break; case attMSGID: value = readMAPIString( d->stream_, false, false, i2 ); d->message_->addProperty( 0x300B, MAPI_TYPE_STRING8, value ); kdDebug() << "Message ID" << " (length=" << i2 << ")" << endl; break; case attOEMCODEPAGE: value = readTNEFData( d->stream_, i2 ); kdDebug() << "Message OEM Code Page" << " (length=" << i2 << ")" << endl; break; default: value = readTNEFAttribute( d->stream_, type, i2 ); kdDebug().form( "Message: type=%x, length=%d, check=%x\n", i1, i2, u ); break; } // skip data if ( d->device_->at() != off && !d->device_->at( off ) ) return false; // get checksum d->stream_ >> u; // add TNEF attribute d->message_->addAttribute( tag, type, value, true ); //kdDebug() << "stream: " << d->device_->at() << endl; return true; } bool KTNEFParser::decodeAttachment() { - Q_UINT32 i; - Q_UINT16 tag, type, u; + quint32 i; + quint16 tag, type, u; QVariant value; QString str; d->stream_ >> i; // i <- attribute type & name tag = ( i & 0x0000FFFF ); type = ( ( i & 0xFFFF0000 ) >> 16 ); d->stream_ >> i; // i <- data length checkCurrent( tag ); switch (tag) { case attATTACHTITLE: value = readMAPIString( d->stream_, false, false, i ); d->current_->setName( value.toString() ); kdDebug() << "Attachment Title: " << d->current_->name() << endl; break; case attATTACHDATA: d->current_->setSize( i ); d->current_->setOffset( d->device_->at() ); d->device_->at( d->device_->at() + i ); value = QString( "< size=%1 >" ).arg( i ); kdDebug() << "Attachment Data: size=" << i << endl; break; case attATTACHMENT: // try to get attachment info i += d->device_->at(); readMAPIProperties( d->current_->properties(), d->current_ ); d->device_->at( i ); d->current_->setIndex( d->current_->property( MAPI_TAG_INDEX ).toUInt() ); d->current_->setDisplaySize( d->current_->property( MAPI_TAG_SIZE ).toUInt() ); str = d->current_->property( MAPI_TAG_DISPLAYNAME ).toString(); if ( !str.isEmpty() ) d->current_->setDisplayName( str ); d->current_->setFileName( d->current_->property( MAPI_TAG_FILENAME ).toString() ); str = d->current_->property( MAPI_TAG_MIMETAG ).toString(); if ( !str.isEmpty() ) d->current_->setMimeTag( str ); d->current_->setExtension( d->current_->property( MAPI_TAG_EXTENSION ).toString() ); value = QString( "< %1 properties >" ).arg( d->current_->properties().count() ); break; case attATTACHMODDATE: value = readTNEFDate( d->stream_ ); kdDebug() << "Attachment Modification Date: " << value.toString() << endl; break; case attATTACHCREATEDATE: value = readTNEFDate( d->stream_ ); kdDebug() << "Attachment Creation Date: " << value.toString() << endl; break; case attATTACHMETAFILE: kdDebug() << "Attachment Metafile: size=" << i << endl; //value = QString( "< size=%1 >" ).arg( i ); //d->device_->at( d->device_->at()+i ); value = readTNEFData( d->stream_, i ); break; default: value = readTNEFAttribute( d->stream_, type, i ); kdDebug().form( "Attachment unknown field: tag=%x, length=%d\n", tag, i); break; } d->stream_ >> u; // u <- checksum // add TNEF attribute d->current_->addAttribute( tag, type, value, true ); //kdDebug() << "stream: " << d->device_->at() << endl; return true; } void KTNEFParser::setDefaultExtractDir(const QString& dirname) { d->defaultdir_ = dirname; } bool KTNEFParser::parseDevice() { - Q_UINT16 u; - Q_UINT32 i; - Q_UINT8 c; + quint16 u; + quint32 i; + quint8 c; d->message_->clearAttachments(); if (d->current_) { delete d->current_; d->current_ = 0; } if ( !d->device_->open( QIODevice::ReadOnly ) ) { kdDebug() << "Couldn't open device" << endl; return false; } d->stream_.setDevice( d->device_ ); d->stream_.setByteOrder( QDataStream::LittleEndian ); d->stream_ >> i; if (i == TNEF_SIGNATURE) { d->stream_ >> u; kdDebug().form( "Attachment cross reference key: 0x%04x\n",u ); //kdDebug() << "stream: " << d->device_->at() << endl; while (!d->stream_.eof()) { d->stream_ >> c; switch (c) { case LVL_MESSAGE: if (!decodeMessage()) goto end; break; case LVL_ATTACHMENT: if (!decodeAttachment()) goto end; break; default: kdDebug() << "Unknown Level: " << c << ", at offset " << d->device_->at() << endl; goto end; } } if (d->current_) { checkCurrent(attATTACHDATA); // this line has the effect to append the // attachment, if it has data. If not it does // nothing, and the attachment will be discarded delete d->current_; d->current_ = 0; } return true; } else { kdDebug() << "This is not a TNEF file" << endl; end: d->device_->close(); return false; } } bool KTNEFParser::extractFile(const QString& filename) { KTNEFAttach *att = d->message_->attachment(filename); if (!att) return false; return extractAttachmentTo(att, d->defaultdir_); } bool KTNEFParser::extractAttachmentTo(KTNEFAttach *att, const QString& dirname) { QString filename = dirname + "/" + att->name(); if (!d->device_->isOpen()) return false; if (!d->device_->at(att->offset())) return false; KSaveFile saveFile( filename ); QFile *outfile = saveFile.file(); if ( !outfile ) return false; - Q_UINT32 len = att->size(), sz(16384); + quint32 len = att->size(), sz(16384); int n(0); char *buf = new char[sz]; bool ok(true); while (ok && len > 0) { - n = d->device_->readBlock(buf,QMIN(sz,len)); + n = d->device_->readBlock(buf,qMin(sz,len)); if (n < 0) ok = false; else { len -= n; if (outfile->writeBlock(buf,n) != n) ok = false; } } delete [] buf; return ok; } bool KTNEFParser::extractAll() { QList l = d->message_->attachmentList(); QList::const_iterator it = l.begin(); for ( ; it != l.end(); ++it ) if (!extractAttachmentTo(*it,d->defaultdir_)) return false; return true; } bool KTNEFParser::extractFileTo(const QString& filename, const QString& dirname) { kdDebug() << "Extracting attachment: filename=" << filename << ", dir=" << dirname << endl; KTNEFAttach *att = d->message_->attachment(filename); if (!att) return false; return extractAttachmentTo(att, dirname); } bool KTNEFParser::openFile(const QString& filename) { deleteDevice(); d->device_ = new QFile( filename ); d->deleteDevice_ = true; return parseDevice(); } bool KTNEFParser::openDevice( QIODevice *device ) { deleteDevice(); d->device_ = device; return parseDevice(); } void KTNEFParser::checkCurrent( int key ) { if ( !d->current_ ) d->current_ = new KTNEFAttach(); else { if ( d->current_->attributes().contains( key ) ) { if (d->current_->offset() >= 0 ) { if (d->current_->name().isEmpty()) d->current_->setName("Unnamed"); if ( d->current_->mimeTag().isEmpty() ) { // No mime type defined in the TNEF structure, // try to find it from the attachment filename // and/or content (using at most 32 bytes) KMimeType::Ptr mimetype; if ( !d->current_->fileName().isEmpty() ) mimetype = KMimeType::findByPath( d->current_->fileName(), 0, true ); if (!mimetype) return; // FIXME if ( mimetype->name() == "application/octet-stream" && d->current_->size() > 0 ) { int oldOffset = d->device_->at(); - QByteArray buffer( QMIN( 32, d->current_->size() ) ); + QByteArray buffer( qMin( 32, d->current_->size() ) ); d->device_->at( d->current_->offset() ); d->device_->readBlock( buffer.data(), buffer.size() ); mimetype = KMimeType::findByContent( buffer ); d->device_->at( oldOffset ); } d->current_->setMimeTag( mimetype->name() ); } d->message_->addAttachment( d->current_ ); d->current_ = 0; } else { // invalid attachment, skip it delete d->current_; d->current_ = 0; } d->current_ = new KTNEFAttach(); } } } //---------------------------------------------------------------------------------------- #define ALIGN( n, b ) if ( n & ( b-1 ) ) { n = ( n + b ) & ~( b-1 ); } #define ISVECTOR( m ) ( ( ( m ).type & 0xF000 ) == MAPI_TYPE_VECTOR ) void clearMAPIName( MAPI_value& mapi ) { mapi.name.value.clear(); } void clearMAPIValue(MAPI_value& mapi, bool clearName) { mapi.value.clear(); if ( clearName ) clearMAPIName( mapi ); } -QDateTime formatTime( Q_UINT32 lowB, Q_UINT32 highB ) +QDateTime formatTime( quint32 lowB, quint32 highB ) { QDateTime dt; #if ( SIZEOF_UINT64_T == 8 ) uint64_t u64; #elif ( SIZEOF_UNSIGNED_LONG_LONG == 8 ) unsigned long long u64; #elif ( SIZEOF_UNSIGNED_LONG == 8 ) unsigned long u64; #else kdWarning() << "Unable to perform date conversion on this system, no 64-bits integer found" << endl; dt.setTime_t( 0xffffffffU ); return dt; #endif u64 = highB; u64 <<= 32; u64 |= lowB; u64 -= 116444736000000000LL; u64 /= 10000000; if ( u64 <= 0xffffffffU ) dt.setTime_t( ( unsigned int )u64 ); else { kdWarning().form( "Invalid date: low byte=0x%08X, high byte=0x%08X\n", lowB, highB ); dt.setTime_t( 0xffffffffU ); } return dt; } QString formatRecipient( const QMap& props ) { QString s, dn, addr, t; QMap::ConstIterator it; if ( ( it = props.find( 0x3001 ) ) != props.end() ) dn = ( *it )->valueString(); if ( ( it = props.find( 0x3003 ) ) != props.end() ) addr = ( *it )->valueString(); if ( ( it = props.find( 0x0C15 ) ) != props.end() ) switch ( ( *it )->value().toInt() ) { case 0: t = "From:"; break; case 1: t = "To:"; break; case 2: t = "Cc:"; break; case 3: t = "Bcc:"; break; } if ( !t.isEmpty() ) s.append( t ); if ( !dn.isEmpty() ) s.append( " " + dn ); if ( !addr.isEmpty() && addr != dn ) s.append( " <" + addr + ">" ); return s.stripWhiteSpace(); } QDateTime readTNEFDate( QDataStream& stream ) { // 14-bytes long - Q_UINT16 y, m, d, hh, mm, ss, dm; + quint16 y, m, d, hh, mm, ss, dm; stream >> y >> m >> d >> hh >> mm >> ss >> dm; return QDateTime( QDate( y, m, d ), QTime( hh, mm, ss ) ); } QString readTNEFAddress( QDataStream& stream ) { - Q_UINT16 totalLen, strLen, addrLen; + quint16 totalLen, strLen, addrLen; QString s; stream >> totalLen >> totalLen >> strLen >> addrLen; s.append( readMAPIString( stream, false, false, strLen ) ); s.append( " <" ); s.append( readMAPIString( stream, false, false, addrLen ) ); s.append( ">" ); - Q_UINT8 c; + quint8 c; for ( int i=8+strLen+addrLen; i> c; return s; } -QByteArray readTNEFData( QDataStream& stream, Q_UINT32 len ) +QByteArray readTNEFData( QDataStream& stream, quint32 len ) { QByteArray array( len ); if ( len > 0 ) stream.readRawBytes( array.data(), len ); return array; } -QVariant readTNEFAttribute( QDataStream& stream, Q_UINT16 type, Q_UINT32 len ) +QVariant readTNEFAttribute( QDataStream& stream, quint16 type, quint32 len ) { switch ( type ) { case atpTEXT: case atpSTRING: return readMAPIString( stream, false, false, len ); case atpDATE: return readTNEFDate( stream ); default: return readTNEFData( stream, len ); } } QString readMAPIString( QDataStream& stream, bool isUnicode, bool align, int len_ ) { - Q_UINT32 len; + quint32 len; char *buf = 0; if ( len_ == -1 ) stream >> len; else len = len_; - Q_UINT32 fullLen = len; + quint32 fullLen = len; if ( align ) ALIGN( fullLen, 4 ); buf = new char[ len ]; stream.readRawBytes( buf, len ); - Q_UINT8 c; + quint8 c; for ( uint i=len; i> c; QString res; if ( isUnicode ) res = QString::fromUcs2( ( const unsigned short* )buf ); else res = QString::fromLocal8Bit( buf ); delete [] buf; return res; } -Q_UINT16 readMAPIValue(QDataStream& stream, MAPI_value& mapi) +quint16 readMAPIValue(QDataStream& stream, MAPI_value& mapi) { - Q_UINT32 d; + quint32 d; clearMAPIValue(mapi); stream >> d; mapi.type = (d & 0x0000FFFF); mapi.tag = ((d & 0xFFFF0000) >> 16); if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) { // skip GUID stream >> d >> d >> d >> d; // name type stream >> mapi.name.type; // name if ( mapi.name.type == 0 ) stream >> mapi.name.value.asUInt(); else if ( mapi.name.type == 1 ) mapi.name.value.asString() = readMAPIString( stream, true ); } int n = 1; QVariant value; if ( ISVECTOR( mapi ) ) { stream >> n; mapi.value = QList(); } for ( int i=0; i> d; value.asUInt() = ( d & 0x0000FFFF ); break; case MAPI_TYPE_BOOLEAN: case MAPI_TYPE_ULONG: stream >> value.asUInt(); break; case MAPI_TYPE_FLOAT: stream >> d; break; case MAPI_TYPE_DOUBLE: stream >> value.asDouble(); break; case MAPI_TYPE_TIME: { - Q_UINT32 lowB, highB; + quint32 lowB, highB; stream >> lowB >> highB; value = formatTime( lowB, highB ); } break; case MAPI_TYPE_STRING8: // in case of a vector'ed value, the number of elements // has already been read in the upper for-loop if ( ISVECTOR( mapi ) ) d = 1; else stream >> d; for (uint i=0;i> d; for (uint i=0;i> len; value = QByteArray( len ); if (len > 0) { int fullLen = len; ALIGN(fullLen, 4); stream.readRawBytes(value.asByteArray().data(), len); - Q_UINT8 c; + quint8 c; for ( int i=len; i> c; } } break; default: mapi.type = MAPI_TYPE_NONE; break; } if ( ISVECTOR( mapi ) ) mapi.value.asList().append( value ); else mapi.value = value; } return mapi.tag; } bool KTNEFParser::readMAPIProperties( QMap& props, KTNEFAttach *attach ) { - Q_UINT32 n; + quint32 n; MAPI_value mapi; KTNEFProperty *p; QMap::ConstIterator it; // some initializations mapi.type = MAPI_TYPE_NONE; mapi.value.clear(); // get number of properties d->stream_ >> n; kdDebug() << "MAPI Properties: " << n << endl; for (uint i=0;istream_.eof()) { clearMAPIValue(mapi); return false; } readMAPIValue(d->stream_, mapi); if (mapi.type == MAPI_TYPE_NONE) { kdDebug().form( "MAPI unsupported: tag=%x, type=%x\n", mapi.tag, mapi.type ); clearMAPIValue(mapi); return false; } int key = mapi.tag; switch (mapi.tag) { case MAPI_TAG_DATA: { if ( mapi.type == MAPI_TYPE_OBJECT && attach ) { QByteArray data = mapi.value.toByteArray(); int len = data.size(); ALIGN( len, 4 ); d->device_->at( d->device_->at()-len ); - Q_UINT32 interface_ID; + quint32 interface_ID; d->stream_ >> interface_ID; if ( interface_ID == MAPI_IID_IMessage ) { // embedded TNEF file attach->unsetDataParser(); attach->setOffset( d->device_->at()+12 ); attach->setSize( data.size()-16 ); attach->setMimeTag( "application/ms-tnef" ); attach->setDisplayName( "Embedded Message" ); kdDebug() << "MAPI Embedded Message: size=" << data.size() << endl; } d->device_->at( d->device_->at() + ( len-4 ) ); break; } } kdDebug().form( "MAPI data: size=%d\n", mapi.value.toByteArray().size() ); break; default: { QString mapiname = ""; if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) { if ( mapi.name.type == 0 ) mapiname = QString().sprintf( " [name = 0x%04x]", mapi.name.value.toUInt() ); else mapiname = QString( " [name = %1]" ).arg( mapi.name.value.toString() ); } switch ( mapi.type & 0x0FFF ) { case MAPI_TYPE_UINT16: kdDebug().form( "(tag=%04x) MAPI short%s: 0x%x\n", mapi.tag, mapiname.ascii(), mapi.value.toUInt() ); break; case MAPI_TYPE_ULONG: kdDebug().form( "(tag=%04x) MAPI long%s: 0x%x\n", mapi.tag, mapiname.ascii(), mapi.value.toUInt() ); break; case MAPI_TYPE_BOOLEAN: kdDebug().form( "(tag=%04x) MAPI boolean%s: %s\n", mapi.tag, mapiname.ascii(), ( mapi.value.toBool() ? "true" : "false" ) ); break; case MAPI_TYPE_TIME: kdDebug().form( "(tag=%04x) MAPI time%s: %s\n", mapi.tag, mapiname.ascii(), mapi.value.toString().ascii() ); break; case MAPI_TYPE_USTRING: case MAPI_TYPE_STRING8: kdDebug().form( "(tag=%04x) MAPI string%s: size=%d \"%s\"\n", mapi.tag, mapiname.ascii(), mapi.value.toByteArray().size(), mapi.value.toString().ascii() ); break; case MAPI_TYPE_BINARY: kdDebug().form( "(tag=%04x) MAPI binary%s: size=%d\n", mapi.tag, mapiname.ascii(), mapi.value.toByteArray().size() ); break; } } break; } // do not remove potential existing similar entry if ( ( it = props.find( key ) ) == props.end() ) { p = new KTNEFProperty( key, ( mapi.type & 0x0FFF ), mapi.value, mapi.name.value ); props[ p->key() ] = p; } //kdDebug() << "stream: " << d->device_->at() << endl; } return true; } diff --git a/ktnef/lib/ktnefproperty.cpp b/ktnef/lib/ktnefproperty.cpp index 3845b8d88..0af55f5d8 100644 --- a/ktnef/lib/ktnefproperty.cpp +++ b/ktnef/lib/ktnefproperty.cpp @@ -1,99 +1,99 @@ /* ktnefproperty.cpp Copyright (C) 2002 Michael Goffioul This file is part of KTNEF, the KDE TNEF support library/program. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "ktnef/ktnefproperty.h" #include "mapi.h" #include #include KTNEFProperty::KTNEFProperty() { } KTNEFProperty::KTNEFProperty( int key_, int type_, const QVariant& value_, const QVariant& name_ ) : _key( key_ ), _type( type_ ), _value( value_ ), _name( name_ ) { } KTNEFProperty::KTNEFProperty( const KTNEFProperty& p ) : _key( p._key ), _type( p._type ), _value( p._value ), _name( p._name ) { } QString KTNEFProperty::keyString() { if ( _name.isValid() ) { if ( _name.type() == QVariant::String ) return _name.asString(); else return mapiNamedTagString( _name.asUInt(), _key ); } else return mapiTagString( _key ); } QString KTNEFProperty::formatValue( const QVariant& value, bool beautify ) { if ( value.type() == QVariant::ByteArray ) { // check the first bytes (up to 8) if they are // printable characters QByteArray arr = value.toByteArray(); bool printable = true; - for ( int i=QMIN( arr.size(), 8 )-1; i>=0 && printable; i-- ) + for ( int i=qMin( arr.size(), 8 )-1; i>=0 && printable; i-- ) printable = ( isprint( arr[ i ] ) != 0 ); if ( !printable ) { QString s; uint i; - uint txtCount = beautify ? QMIN( arr.size(), 32 ) : arr.size(); + uint txtCount = beautify ? qMin( arr.size(), 32 ) : arr.size(); for ( i=0; i < txtCount; ++i ) { s.append( QString().sprintf( "%02X", ( uchar )arr[ i ] ) ); if( beautify ) s.append( " " ); } if ( i < arr.size() ) s.append( "... (size=" + QString::number( arr.size() ) + ")" ); return s; } } //else if ( value.type() == QVariant::DateTime ) // return value.toDateTime().toString(); return value.toString(); } QString KTNEFProperty::valueString() { return formatValue( _value ); } int KTNEFProperty::key() const { return _key; } int KTNEFProperty::type() const { return _type; } QVariant KTNEFProperty::value() const { return _value; } QVariant KTNEFProperty::name() const { return _name; } bool KTNEFProperty::isVector() const { return ( _value.type() == QVariant::List ); } diff --git a/ktnef/lib/ktnefwriter.cpp b/ktnef/lib/ktnefwriter.cpp index 0abbfcb3f..f52cb669f 100644 --- a/ktnef/lib/ktnefwriter.cpp +++ b/ktnef/lib/ktnefwriter.cpp @@ -1,500 +1,500 @@ /* ktnefwriter.cpp Copyright (C) 2002 Bo Thorsen This file is part of KTNEF, the KDE TNEF support library/program. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include "ktnef/ktnefwriter.h" #include "ktnef/ktnefproperty.h" #include "ktnef/ktnefpropertyset.h" #include #include #include //Added by qt3to4: #include #include #include #include #include "ktnef/ktnefdefs.h" class KTNEFWriter::PrivateData { public: PrivateData() { mFirstAttachNum = QDateTime::currentDateTime().toTime_t(); } KTNEFPropertySet properties; - Q_UINT16 mFirstAttachNum; + quint16 mFirstAttachNum; }; KTNEFWriter::KTNEFWriter() { mData = new PrivateData; // This is not something the user should fiddle with // First set the TNEF version QVariant v(0x00010000); addProperty( attTNEFVERSION, atpDWORD, v ); // Now set the code page to something reasonable. TODO: Use the right one - QVariant v1( (Q_UINT32)0x4e4 ); - QVariant v2( (Q_UINT32)0x0 ); + QVariant v1( (quint32)0x4e4 ); + QVariant v2( (quint32)0x0 ); QList list; list << v1; list << v2; v = QVariant( list ); addProperty( attOEMCODEPAGE, atpBYTE, list ); } KTNEFWriter::~KTNEFWriter() { delete mData; } void KTNEFWriter::addProperty( int tag, int type, const QVariant& value ) { mData->properties.addProperty( tag, type, value ); } -void addToChecksum( Q_UINT32 i, Q_UINT16 &checksum ) { +void addToChecksum( quint32 i, quint16 &checksum ) { checksum += i & 0xff; checksum += (i >> 8) & 0xff; checksum += (i >> 16) & 0xff; checksum += (i >> 24) & 0xff; } -void addToChecksum( QByteArray &cs, Q_UINT16 &checksum ) { +void addToChecksum( QByteArray &cs, quint16 &checksum ) { int len = cs.length(); for (int i=0; i& properties = mData->properties.properties(); QMap::Iterator it = properties.find( tag ); if ( it == properties.end() ) return false; KTNEFProperty *property = *it; - Q_UINT32 i; - Q_UINT16 checksum = 0; + quint32 i; + quint16 checksum = 0; QList list; QString s; QByteArray cs, cs2; QDateTime dt; QDate date; QTime time; switch( tag ) { case attMSGSTATUS: - // Q_UINT8 + // quint8 i = property->value().toUInt() & 0xff; checksum = i; - stream << (Q_UINT8)LVL_MESSAGE; + stream << (quint8)LVL_MESSAGE; stream << mergeTagAndType( tag, property->type() ); - stream << (Q_UINT32)1; - stream << (Q_UINT8)i; + stream << (quint32)1; + stream << (quint8)i; bytes += 10; break; case attMSGPRIORITY: case attREQUESTRES: - // Q_UINT16 + // quint16 i = property->value().toUInt() & 0xffff; addToChecksum( i, checksum ); - stream << (Q_UINT8)LVL_MESSAGE; + stream << (quint8)LVL_MESSAGE; stream << mergeTagAndType( tag, property->type() ); - stream << (Q_UINT32)2; - stream << (Q_UINT16)i; + stream << (quint32)2; + stream << (quint16)i; bytes += 11; break; case attTNEFVERSION: - // Q_UINT32 + // quint32 i = property->value().toUInt(); addToChecksum( i, checksum ); - stream << (Q_UINT8)LVL_MESSAGE; + stream << (quint8)LVL_MESSAGE; stream << mergeTagAndType( tag, property->type() ); - stream << (Q_UINT32)4; - stream << (Q_UINT32)i; + stream << (quint32)4; + stream << (quint32)i; bytes += 13; break; case attOEMCODEPAGE: - // 2 Q_UINT32 + // 2 quint32 list = property->value().toList(); assert( list.count() == 2 ); - stream << (Q_UINT8)LVL_MESSAGE; + stream << (quint8)LVL_MESSAGE; stream << mergeTagAndType( tag, property->type() ); - stream << (Q_UINT32)8; + stream << (quint32)8; i = list[0].toInt(); addToChecksum( i, checksum ); - stream << (Q_UINT32)i; + stream << (quint32)i; i = list[1].toInt(); addToChecksum( i, checksum ); - stream << (Q_UINT32)i; + stream << (quint32)i; bytes += 17; break; case attMSGCLASS: case attSUBJECT: case attBODY: case attMSGID: // QCString cs = property->value().toString().local8Bit(); addToChecksum( cs, checksum ); - stream << (Q_UINT8)LVL_MESSAGE; + stream << (quint8)LVL_MESSAGE; stream << mergeTagAndType( tag, property->type() ); - stream << (Q_UINT32)cs.length()+1; + stream << (quint32)cs.length()+1; writeCString( stream, cs ); bytes += 9 + cs.length()+1; break; case attFROM: // 2 QString encoded to a TRP structure list = property->value().toList(); assert( list.count() == 2 ); cs = list[0].toString().local8Bit(); // Name cs2 = (QString("smtp:") + list[1].toString()).local8Bit(); // Email address i = 18 + cs.length() + cs2.length(); // 2 * sizof(TRP) + strings + 2x'\0' - stream << (Q_UINT8)LVL_MESSAGE; + stream << (quint8)LVL_MESSAGE; stream << mergeTagAndType( tag, property->type() ); - stream << (Q_UINT32)i; + stream << (quint32)i; // The stream has to be aligned to 4 bytes for the strings // TODO: Or does it? Looks like Outlook doesn't do this // bytes += 17; // Write the first TRP structure - stream << (Q_UINT16)4; // trpidOneOff - stream << (Q_UINT16)i; // totalsize - stream << (Q_UINT16)(cs.length()+1); // sizeof name - stream << (Q_UINT16)(cs2.length()+1); // sizeof address + stream << (quint16)4; // trpidOneOff + stream << (quint16)i; // totalsize + stream << (quint16)(cs.length()+1); // sizeof name + stream << (quint16)(cs2.length()+1); // sizeof address // if ( bytes % 4 != 0 ) // Align the buffer // Write the strings writeCString( stream, cs ); writeCString( stream, cs2 ); // Write the empty padding TRP structure (just zeroes) - stream << (Q_UINT32)0 << (Q_UINT32)0; + stream << (quint32)0 << (quint32)0; addToChecksum( 4, checksum ); addToChecksum( i, checksum ); addToChecksum( cs.length()+1, checksum ); addToChecksum( cs2.length()+1, checksum ); addToChecksum( cs, checksum ); addToChecksum( cs2, checksum ); bytes += 10; break; case attDATESENT: case attDATERECD: case attDATEMODIFIED: // QDateTime dt = property->value().toDateTime(); time = dt.time(); date = dt.date(); - stream << (Q_UINT8)LVL_MESSAGE; + stream << (quint8)LVL_MESSAGE; stream << mergeTagAndType( tag, property->type() ); - stream << (Q_UINT32)14; + stream << (quint32)14; - i = (Q_UINT16)date.year(); + i = (quint16)date.year(); addToChecksum( i, checksum ); - stream << (Q_UINT16)i; - i = (Q_UINT16)date.month(); + stream << (quint16)i; + i = (quint16)date.month(); addToChecksum( i, checksum ); - stream << (Q_UINT16)i; - i = (Q_UINT16)date.day(); + stream << (quint16)i; + i = (quint16)date.day(); addToChecksum( i, checksum ); - stream << (Q_UINT16)i; - i = (Q_UINT16)time.hour(); + stream << (quint16)i; + i = (quint16)time.hour(); addToChecksum( i, checksum ); - stream << (Q_UINT16)i; - i = (Q_UINT16)time.minute(); + stream << (quint16)i; + i = (quint16)time.minute(); addToChecksum( i, checksum ); - stream << (Q_UINT16)i; - i = (Q_UINT16)time.second(); + stream << (quint16)i; + i = (quint16)time.second(); addToChecksum( i, checksum ); - stream << (Q_UINT16)i; - i = (Q_UINT16)date.dayOfWeek(); + stream << (quint16)i; + i = (quint16)date.dayOfWeek(); addToChecksum( i, checksum ); - stream << (Q_UINT16)i; + stream << (quint16)i; break; /* case attMSGSTATUS: { - Q_UINT8 c; - Q_UINT32 flag = 0; + quint8 c; + quint32 flag = 0; if ( c & fmsRead ) flag |= MSGFLAG_READ; if ( !( c & fmsModified ) ) flag |= MSGFLAG_UNMODIFIED; if ( c & fmsSubmitted ) flag |= MSGFLAG_SUBMIT; if ( c & fmsHasAttach ) flag |= MSGFLAG_HASATTACH; if ( c & fmsLocal ) flag |= MSGFLAG_UNSENT; d->stream_ >> c; i = property->value().toUInt(); - stream << (Q_UINT8)LVL_MESSAGE; - stream << (Q_UINT32)type; - stream << (Q_UINT32)2; - stream << (Q_UINT8)i; + stream << (quint8)LVL_MESSAGE; + stream << (quint32)type; + stream << (quint32)2; + stream << (quint8)i; addToChecksum( i, checksum ); // from reader: d->message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag ); } kdDebug() << "Message Status" << " (length=" << i2 << ")" << endl; break; */ default: kdDebug() << "Unknown TNEF tag: " << tag << endl; return false; } - stream << (Q_UINT16)checksum; + stream << (quint16)checksum; return true; } bool KTNEFWriter::writeFile( QIODevice &file ) { if ( !file.open( QIODevice::WriteOnly ) ) return false; QDataStream stream( &file ); return writeFile( stream ); } bool KTNEFWriter::writeFile( QDataStream &stream ) { stream.setByteOrder( QDataStream::LittleEndian ); // Start by writing the opening TNEF stuff stream << TNEF_SIGNATURE; // Store the PR_ATTACH_NUM value for the first attachment // ( must be stored even if *no* attachments are stored ) stream << mData->mFirstAttachNum; // Now do some writing bool ok = true; int bytesWritten = 0; ok &= writeProperty( stream, bytesWritten, attTNEFVERSION ); ok &= writeProperty( stream, bytesWritten, attOEMCODEPAGE ); ok &= writeProperty( stream, bytesWritten, attMSGCLASS ); ok &= writeProperty( stream, bytesWritten, attMSGPRIORITY ); ok &= writeProperty( stream, bytesWritten, attSUBJECT ); ok &= writeProperty( stream, bytesWritten, attDATESENT ); ok &= writeProperty( stream, bytesWritten, attDATESTART ); ok &= writeProperty( stream, bytesWritten, attDATEEND ); // ok &= writeProperty( stream, bytesWritten, attAIDOWNER ); ok &= writeProperty( stream, bytesWritten, attREQUESTRES ); ok &= writeProperty( stream, bytesWritten, attFROM ); ok &= writeProperty( stream, bytesWritten, attDATERECD ); ok &= writeProperty( stream, bytesWritten, attMSGSTATUS ); ok &= writeProperty( stream, bytesWritten, attBODY ); return ok; } void KTNEFWriter::setSender(const QString &name, const QString &email) { assert( !name.isEmpty() ); assert( !email.isEmpty() ); QVariant v1( name ); QVariant v2( email ); QList list; list << v1; list << v2; QVariant v( list ); addProperty( attFROM, 0, list ); // What's up with the 0 here ?? } void KTNEFWriter::setMessageType(MessageType m) { // Note that the MessageType list here is probably not long enough, // more entries are most likely needed later QVariant v; switch( m ) { case Appointment: v = QVariant( QString( "IPM.Appointment" ) ); break; case MeetingCancelled: v = QVariant( QString( "IPM.Schedule.Meeting.Cancelled" ) ); break; case MeetingRequest: v = QVariant( QString( "IPM.Schedule.Meeting.Request" ) ); break; case MeetingNo: v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Neg" ) ); break; case MeetingYes: v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Pos" ) ); break; case MeetingTent: // Tent? v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Tent" ) ); break; default: return; } addProperty( attMSGCLASS, atpWORD, v ); } void KTNEFWriter::setMethod( Method ) { } void KTNEFWriter::clearAttendees() { } void KTNEFWriter::addAttendee( const QString& /*cn*/, Role /*r*/, PartStat /*p*/, bool /*rsvp*/, const QString& /*mailto*/ ) { } // I assume this is the same as the sender? // U also assume that this is like "Name
    " void KTNEFWriter::setOrganizer( const QString& organizer ) { int i = organizer.find( '<' ); if ( i == -1 ) return; QString name = organizer.left( i ); name.stripWhiteSpace(); QString email = organizer.right( i+1 ); email = email.left( email.length()-1 ); email.stripWhiteSpace(); setSender( name, email ); } void KTNEFWriter::setDtStart( const QDateTime& dtStart ) { QVariant v( dtStart ); addProperty( attDATESTART, atpDATE, v ); } void KTNEFWriter::setDtEnd( const QDateTime& dtEnd ) { QVariant v( dtEnd ); addProperty( attDATEEND, atpDATE, v ); } void KTNEFWriter::setLocation( const QString& /*location*/ ) { } void KTNEFWriter::setUID( const QString& uid ) { QVariant v( uid ); addProperty( attMSGID, atpSTRING, v ); } // Date sent void KTNEFWriter::setDtStamp( const QDateTime& dtStamp ) { QVariant v( dtStamp ); addProperty( attDATESENT, atpDATE, v ); } void KTNEFWriter::setCategories( const QStringList& ) { } // I hope this is the body void KTNEFWriter::setDescription( const QString &body ) { QVariant v( body ); addProperty( attBODY, atpTEXT, v ); } void KTNEFWriter::setSummary( const QString &s ) { QVariant v( s ); addProperty( attSUBJECT, atpSTRING, v ); } // TNEF encoding: Normal = 3, high = 2, low = 1 // MAPI encoding: Normal = -1, high = 0, low = 1 void KTNEFWriter::setPriority( Priority p ) { - QVariant v( (Q_UINT32)p ); + QVariant v( (quint32)p ); addProperty( attMSGPRIORITY, atpSHORT, v ); } void KTNEFWriter::setAlarm( const QString& /*description*/, AlarmAction /*action*/, const QDateTime& /*wakeBefore*/ ) { } diff --git a/qgpgme/dataprovider.cpp b/qgpgme/dataprovider.cpp index ab75fc8f5..0b872c149 100644 --- a/qgpgme/dataprovider.cpp +++ b/qgpgme/dataprovider.cpp @@ -1,108 +1,108 @@ /* dataprovider.cpp - Copyright (C) 2004 Klarälvdalens Datakonsult AB + Copyright (C) 2004 Klar�vdalens Datakonsult AB This file is part of QGPGME. QGPGME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. QGPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QGPGME; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // -*- c++ -*- #include #include #include #include #include #include static bool resizeAndInit( QByteArray & ba, size_t newSize ) { const size_t oldSize = ba.size(); ba.resize( newSize ); const bool ok = ( newSize == static_cast( ba.size() ) ); if ( ok ) memset( ba.data() + oldSize, 0, newSize - oldSize ); return ok; } QGpgME::QByteArrayDataProvider::QByteArrayDataProvider() : GpgME::DataProvider(), mOff( 0 ) {} QGpgME::QByteArrayDataProvider::QByteArrayDataProvider( const QByteArray & initialData ) : GpgME::DataProvider(), mArray( initialData ), mOff( 0 ) {} QGpgME::QByteArrayDataProvider::~QByteArrayDataProvider() {} ssize_t QGpgME::QByteArrayDataProvider::read( void * buffer, size_t bufSize ) { #ifndef NDEBUG qDebug( "QGpgME::QByteArrayDataProvider::read( %p, %d )", buffer, bufSize ); #endif if ( bufSize == 0 ) return 0; if ( mOff >= mArray.size() ) return 0; // EOF - size_t amount = QMIN( bufSize, static_cast( mArray.size() - mOff ) ); + size_t amount = qMin( bufSize, static_cast( mArray.size() - mOff ) ); assert( amount > 0 ); memcpy( buffer, mArray.data() + mOff, amount ); mOff += amount; return amount; } ssize_t QGpgME::QByteArrayDataProvider::write( const void * buffer, size_t bufSize ) { #ifndef NDEBUG qDebug( "QGpgME::QByteArrayDataProvider::write( %p, %d )", buffer, bufSize ); #endif if ( bufSize == 0 ) return 0; if ( mOff >= mArray.size() ) resizeAndInit( mArray, mOff + bufSize ); if ( mOff >= mArray.size() ) { errno = EIO; return -1; } assert( bufSize <= mArray.size() - mOff ); memcpy( mArray.data() + mOff, buffer, bufSize ); mOff += bufSize; return bufSize; } off_t QGpgME::QByteArrayDataProvider::seek( off_t offset, int whence ) { #ifndef NDEBUG qDebug( "QGpgME::QByteArrayDataProvider::seek( %d, %d )", int(offset), whence ); #endif int newOffset = mOff; switch ( whence ) { case SEEK_SET: newOffset = offset; break; case SEEK_CUR: newOffset += offset; break; case SEEK_END: newOffset = mArray.size() + offset; break; default: errno = EINVAL; return (off_t)-1; } return mOff = newOffset; } void QGpgME::QByteArrayDataProvider::release() { #ifndef NDEBUG qDebug( "QGpgME::QByteArrayDataProvider::release()" ); #endif mArray = QByteArray(); }