diff --git a/kmail/kmfoldermaildir.cpp b/kmail/kmfoldermaildir.cpp index badc074796..a06826f63a 100644 --- a/kmail/kmfoldermaildir.cpp +++ b/kmail/kmfoldermaildir.cpp @@ -1,932 +1,945 @@ // kmfoldermaildir.cpp // Author: Kurt Granroth #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "kfileio.h" #include "kmfoldermaildir.h" #include "kmfoldermgr.h" #include "kmmessage.h" #include "kmundostack.h" #include "kbusyptr.h" #include "maildirjob.h" using KMail::MaildirJob; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef isblank # define isblank(x) ((x)==' '||(x)=='\t') #endif #ifndef MAX_LINE #define MAX_LINE 4096 #endif #ifndef INIT_MSGS #define INIT_MSGS 8 #endif //----------------------------------------------------------------------------- KMFolderMaildir::KMFolderMaildir(KMFolderDir* aParent, const QString& aName) : KMFolderMaildirInherited(aParent, aName) { } //----------------------------------------------------------------------------- KMFolderMaildir::~KMFolderMaildir() { if (mOpenCount>0) close(TRUE); if (kernel->undoStack()) kernel->undoStack()->folderDestroyed(this); } //----------------------------------------------------------------------------- int KMFolderMaildir::canAccess() { assert(!name().isEmpty()); if (access(QFile::encodeName(location()), R_OK | W_OK | X_OK) != 0) return 1; if (access(QFile::encodeName(location() + "/new"), R_OK | W_OK | X_OK) != 0) return 1; if (access(QFile::encodeName(location() + "/cur"), R_OK | W_OK | X_OK) != 0) return 1; if (access(QFile::encodeName(location() + "/tmp"), R_OK | W_OK | X_OK) != 0) return 1; return 0; } //----------------------------------------------------------------------------- int KMFolderMaildir::open() { int rc = 0; mOpenCount++; if (mOpenCount > 1) return 0; // already open assert(!name().isEmpty()); if (canAccess() != 0) { bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Error opening %1. Either this is not a valid " "maildir folder or you don't have sufficient access permissions.") .arg(name())); if (busy) kernel->kbp()->busy(); return EPERM; } if (!path().isEmpty()) { if (KMFolderIndex::IndexOk != indexStatus()) // test if contents file has changed { QString str; mIndexStream = 0; str = i18n("Folder `%1' changed. Recreating index.") .arg(name()); emit statusMsg(str); } else { mIndexStream = fopen(indexLocation().local8Bit(), "r+"); // index file updateIndexStreamPtr(); } if (!mIndexStream) rc = createIndexFromContents(); else readIndex(); } else { mAutoCreateIndex = FALSE; rc = createIndexFromContents(); } mQuiet = 0; mChanged = FALSE; //readConfig(); return rc; } //----------------------------------------------------------------------------- int KMFolderMaildir::create(bool imap) { int rc; int old_umask; assert(!name().isEmpty()); assert(mOpenCount == 0); // create the maildir directory structure if (::mkdir(QFile::encodeName(location()), S_IRWXU) > 0) { kdDebug(5006) << "Could not create " << location() << " maildir" << endl; return errno; } if (::mkdir(QFile::encodeName(location() + "/new"), S_IRWXU) > 0) { kdDebug(5006) << "Could not create " << location() << "/new" << endl; return errno; } if (::mkdir(QFile::encodeName(location() + "/cur"), S_IRWXU) > 0) { kdDebug(5006) << "Could not create " << location() << "/cur" << endl; return errno; } if (::mkdir(QFile::encodeName(location() + "/tmp"), S_IRWXU) > 0) { kdDebug(5006) << "Could not create " << location() << "/new" << endl; return errno; } if (!path().isEmpty()) { old_umask = umask(077); mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW updateIndexStreamPtr(TRUE); umask(old_umask); if (!mIndexStream) return errno; } else { mAutoCreateIndex = FALSE; } mOpenCount++; mQuiet = 0; mChanged = FALSE; if (imap) { readConfig(); mUnreadMsgs = -1; } rc = writeIndex(); return rc; } //----------------------------------------------------------------------------- void KMFolderMaildir::close(bool aForced) { if (mOpenCount <= 0) return; if (mOpenCount > 0) mOpenCount--; if (mOpenCount > 0 && !aForced) return; if ((this != kernel->inboxFolder()) && isSystemFolder() && !aForced) return; if (mAutoCreateIndex) { updateIndex(); writeConfig(); } mMsgList.clear(TRUE); if (mIndexStream) { fclose(mIndexStream); updateIndexStreamPtr(TRUE); } mOpenCount = 0; mIndexStream = 0; mUnreadMsgs = -1; mMsgList.reset(INIT_MSGS); } //----------------------------------------------------------------------------- void KMFolderMaildir::sync() { if (mOpenCount > 0) if (!mIndexStream || fsync(fileno(mIndexStream))) { kernel->emergencyExit( i18n("Couldn't sync maildir folder.") ); } } //----------------------------------------------------------------------------- int KMFolderMaildir::expungeContents() { // nuke all messages in this folder now QDir d(location() + "/new"); d.setFilter(QDir::Files); QStringList files(d.entryList()); QStringList::ConstIterator it(files.begin()); for ( ; it != files.end(); ++it) QFile::remove(d.filePath(*it)); d.setPath(location() + "/cur"); files = d.entryList(); for (it = files.begin(); it != files.end(); ++it) QFile::remove(d.filePath(*it)); return 0; } //----------------------------------------------------------------------------- int KMFolderMaildir::compact() { if (needsCompact == false) return 0; open(); QString subdirNew(location() + "/new/"); QString subdirCur(location() + "/cur/"); for (int i = 0; i < count(); i++) { KMMsgInfo *mi = (KMMsgInfo*)mMsgList[i]; if (!mi) continue; QString filename(mi->fileName()); if (filename.isEmpty()) continue; // first, make sure this isn't in the 'new' subdir QString newFile(subdirNew + filename); if (QFile::exists(newFile)) moveInternal(subdirNew + filename, subdirCur + filename, mi); // construct a valid filename. if it's already valid, then // nothing happens constructValidFileName(filename, mi->status()); // if the name changed, then we need to update the actual filename if (filename != mi->fileName()) { moveInternal(subdirCur + mi->fileName(), subdirCur + filename, mi); mi->setFileName(filename); setDirty( true ); } // we can't have any New messages at this point if (mi->status() == KMMsgStatusNew) { mi->setStatus(KMMsgStatusUnread); setDirty( true ); } } close(); needsCompact = false; return 0; } //------------------------------------------------------------- FolderJob* KMFolderMaildir::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder ) const { MaildirJob *job = new MaildirJob( msg, jt, folder ); job->setParentFolder( this ); return job; } //------------------------------------------------------------- FolderJob* KMFolderMaildir::doCreateJob( QPtrList& msgList, const QString& sets, FolderJob::JobType jt, KMFolder *folder ) const { MaildirJob *job = new MaildirJob( msgList, sets, jt, folder ); job->setParentFolder( this ); return job; } //------------------------------------------------------------- int KMFolderMaildir::addMsg(KMMessage* aMsg, int* index_return) { /* QFile fileD0( "testdat_xx-kmfoldermaildir-0" ); if( fileD0.open( IO_WriteOnly ) ) { QDataStream ds( &fileD0 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD0.close(); // If data is 0 we just create a zero length file. } */ if (!canAddMsgNow(aMsg, index_return)) return 0; long len; unsigned long size; bool opened = FALSE; KMFolder* msgParent; QCString msgText; int idx(-1); int rc; // take message out of the folder it is currently in, if any msgParent = aMsg->parent(); if (msgParent) { if (msgParent==this && !kernel->folderIsDraftOrOutbox(this)) return 0; idx = msgParent->find(aMsg); msgParent->getMsg( idx ); } aMsg->setStatusFields(); if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by aMsg->removeHeaderField("Content-Type"); // the line above msgText = aMsg->asString(); len = msgText.length(); if (len <= 0) { kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl; return 0; } // make sure the filename has the correct extension QString filename(aMsg->fileName()); constructValidFileName(filename, aMsg->status()); QString tmp_file(location() + "/tmp/"); tmp_file += filename; if (!kCStringToFile(msgText, tmp_file, false, false, false)) kernel->emergencyExit( i18n("Not enough free disk space.") ); QFile file(tmp_file); size = msgText.length(); if (!isOpened()) { opened = TRUE; rc = open(); kdDebug(5006) << "addMsg-open: " << rc << endl; if (rc) return rc; } // now move the file to the correct location QString new_loc(location() + "/cur/"); new_loc += filename; if (moveInternal(tmp_file, new_loc, filename, aMsg->status()).isNull()) { file.remove(); if (opened) close(); return -1; } if (msgParent) if (idx >= 0) msgParent->take(idx); if (filename != aMsg->fileName()) aMsg->setFileName(filename); if (aMsg->status() == KMMsgStatusUnread || aMsg->status() == KMMsgStatusNew || this == kernel->outboxFolder()) { if (mUnreadMsgs == -1) mUnreadMsgs = 1; else ++mUnreadMsgs; emit numUnreadMsgsChanged( this ); } ++mTotalMsgs; // store information about the position in the folder file in the message aMsg->setParent(this); aMsg->setMsgSize(size); idx = mMsgList.append(aMsg); if (aMsg->getMsgSerNum() <= 0) aMsg->setMsgSerNum(); // write index entry if desired if (mAutoCreateIndex) { assert(mIndexStream != 0); clearerr(mIndexStream); fseek(mIndexStream, 0, SEEK_END); off_t revert = ftell(mIndexStream); int len; const uchar *buffer = aMsg->asIndexString(len); fwrite(&len,sizeof(len), 1, mIndexStream); aMsg->setIndexOffset( ftell(mIndexStream) ); aMsg->setIndexLength( len ); if(fwrite(buffer, len, 1, mIndexStream) != 1) kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; fflush(mIndexStream); int error = ferror(mIndexStream); error |= appendtoMsgDict(idx); if (error) { kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; if (ftell(mIndexStream) > revert) { kdDebug(5006) << "Undoing changes" << endl; truncate( QFile::encodeName(indexLocation()), revert ); } kernel->emergencyExit(i18n("KMFolderMaildir::addMsg: abnormally terminating to prevent data loss.")); // exit(1); // don't ever use exit(), use the above! /* This code may not be 100% reliable bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Unable to add message to folder.\n" "(No space left on device or insufficient quota?)\n" "Free space and sufficient quota are required to continue safely.")); if (busy) kernel->kbp()->busy(); if (opened) close(); */ return error; } } // some "paper work" if (index_return) *index_return = idx; emitMsgAddedSignals(idx); needsCompact = true; if (opened) close(); /* QFile fileD1( "testdat_xx-kmfoldermaildir-1" ); if( fileD1.open( IO_WriteOnly ) ) { QDataStream ds( &fileD1 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD1.close(); // If data is 0 we just create a zero length file. } */ return 0; } KMMessage* KMFolderMaildir::readMsg(int idx) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; KMMessage *msg = new KMMessage(*mi); msg->fromDwString(getDwString(idx)); mMsgList.set(idx,msg); return msg; } DwString KMFolderMaildir::getDwString(int idx) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; QString abs_file(location() + "/cur/"); abs_file += mi->fileName(); if (QFile::exists(abs_file)) { FILE* stream = fopen(abs_file.local8Bit(), "r+"); DwString str( stream, mi->msgSize() ); fclose( stream ); return str; } return DwString(); } QCString& KMFolderMaildir::getMsgString(int idx, QCString& mDest) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0); QString abs_file(location() + "/cur/"); abs_file += mi->fileName(); if (QFile::exists(abs_file) == false) { kdDebug(5006) << "The " << abs_file << " file doesn't exist!" << endl; return mDest; } mDest.resize(mi->msgSize()+2); mDest = kFileToString(abs_file, false, false); return mDest; } void KMFolderMaildir::readFileHeaderIntern(const QString& dir, const QString& file, KMMsgStatus status) { // we keep our current directory to restore it later char path_buffer[PATH_MAX]; ::getcwd(path_buffer, PATH_MAX - 1); ::chdir(dir.local8Bit()); // messages in the 'cur' directory are Read by default.. but may // actually be some other state (but not New) if (status == KMMsgStatusRead) { if (file.find(":2,") == -1) status = KMMsgStatusUnread; else if (file.right(5) == ":2,RS") status = KMMsgStatusReplied; } // open the file and get a pointer to it QFile f(file); if (f.open(IO_ReadOnly) == false) return; char line[MAX_LINE]; bool atEof = false; bool inHeader = true; QCString *lastStr = 0; QCString dateStr, fromStr, toStr, subjStr; QCString xmarkStr, replyToIdStr, msgIdStr, referencesStr; - QCString statusStr; + QCString statusStr, replyToAuxIdStr; // iterate through this file until done while (!atEof) { // if the end of the file has been reached or if there was an error if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) ) atEof = true; // are we done with this file? if so, compile our info and store // it in a KMMsgInfo object if (atEof || !inHeader) { if ((replyToIdStr.isEmpty() || (replyToIdStr[0] != '<')) && !referencesStr.isEmpty() && referencesStr[0] == '<') { - replyToIdStr = referencesStr; + // use the last reference, instead of missing In-Reply-To + int leftAngle = referencesStr.findRev( '<' ); + if (leftAngle != -1) + replyToIdStr = referencesStr.mid(leftAngle); } if (!statusStr.isEmpty()) { // only handle those states not determined by the file suffix if (statusStr[0] == 'S') status = KMMsgStatusSent; else if (statusStr[0] == 'F') status = KMMsgStatusForwarded; else if (statusStr[0] == 'D') status = KMMsgStatusDeleted; else if (statusStr[0] == 'Q') status = KMMsgStatusQueued; else if (statusStr[0] == 'G') status = KMMsgStatusFlag; } KMMsgInfo *mi = new KMMsgInfo(this); - mi->init(subjStr, fromStr, toStr, 0, status, xmarkStr, replyToIdStr, - msgIdStr, file.local8Bit(), KMMsgEncryptionStateUnknown, - KMMsgSignatureStateUnknown, KMMsgMDNStateUnknown, f.size()); + mi->init(subjStr, fromStr, toStr, 0, status, xmarkStr, replyToIdStr, + replyToAuxIdStr, msgIdStr, file.local8Bit(), + KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, + KMMsgMDNStateUnknown, f.size()); if (!dateStr.isEmpty()) mi->setDate(dateStr); mi->setDirty(false); mMsgList.append(mi); // if this is a New file and is in 'new', we move it to 'cur' if (status == KMMsgStatusNew) { QString newDir(location() + "/new/"); QString curDir(location() + "/cur/"); moveInternal(newDir + file, curDir + file, mi); } break; } // Is this a long header line? if (inHeader && line[0] == '\t' || line[0] == ' ') { int i = 0; while (line[i] == '\t' || line[i] == ' ') i++; if (line[i] < ' ' && line[i] > 0) inHeader = false; else if (lastStr) *lastStr += line + i; } else lastStr = 0; if (inHeader && (line[0] == '\n' || line[0] == '\r')) inHeader = false; if (!inHeader) continue; if (strncasecmp(line, "Date:", 5) == 0 && isblank(line[5])) { dateStr = QCString(line+6); lastStr = &dateStr; } else if (strncasecmp(line, "From:", 5) == 0 && isblank(line[5])) { fromStr = QCString(line+6); lastStr = &fromStr; } else if (strncasecmp(line, "To:", 3) == 0 && isblank(line[3])) { toStr = QCString(line+4); lastStr = &toStr; } else if (strncasecmp(line, "Subject:", 8) == 0 && isblank(line[8])) { subjStr = QCString(line+9); lastStr = &subjStr; } else if (strncasecmp(line, "References:", 11) == 0 && isblank(line[11])) { int leftAngle, rightAngle; referencesStr = QCString(line+12); leftAngle = referencesStr.findRev('<'); + leftAngle = referencesStr.findRev( '<', leftAngle-1); if (leftAngle != -1) referencesStr = referencesStr.mid(leftAngle); + rightAngle = referencesStr.findRev( '>' ); + if (rightAngle != -1) + referencesStr.truncate( rightAngle + 1 ); + // Store the second to last reference in the replyToAuxIdStr + // It is a good candidate for threading the message below if the + // message In-Reply-To points to is not kept in this folder, + // but e.g. in an Outbox + replyToAuxIdStr = referencesStr; rightAngle = referencesStr.find('>'); if (rightAngle != -1) - referencesStr.truncate(rightAngle + 1); + replyToAuxIdStr.truncate( rightAngle + 1 ); } else if (strncasecmp(line, "Message-Id:", 11) == 0 && isblank(line[11])) { int rightAngle; msgIdStr = QCString(line+12); rightAngle = msgIdStr.find( '>' ); if (rightAngle != -1) msgIdStr.truncate(rightAngle + 1); } else if (strncasecmp(line, "X-KMail-Mark:", 13) == 0 && isblank(line[13])) { xmarkStr = QCString(line+14); } else if (strncasecmp(line, "X-Status:", 9) == 0 && isblank(line[9])) { statusStr = QCString(line+10); } else if (strncasecmp(line, "In-Reply-To:", 12) == 0 && isblank(line[12])) { int rightAngle; replyToIdStr = QCString(line+13); rightAngle = replyToIdStr.find( '>' ); if (rightAngle != -1) replyToIdStr.truncate( rightAngle + 1 ); } } if (status == KMMsgStatusNew || status == KMMsgStatusUnread || (this == kernel->outboxFolder())) { mUnreadMsgs++; if (mUnreadMsgs == 0) ++mUnreadMsgs; } ::chdir(path_buffer); } int KMFolderMaildir::createIndexFromContents() { mUnreadMsgs = 0; mMsgList.clear(true); mMsgList.reset(INIT_MSGS); mChanged = false; // first, we make sure that all the directories are here as they // should be QFileInfo dirinfo; dirinfo.setFile(location() + "/new"); if (!dirinfo.exists() || !dirinfo.isDir()) { kdDebug(5006) << "Directory " << location() << "/new doesn't exist or is a file"<< endl; return 1; } QDir newDir(location() + "/new"); newDir.setFilter(QDir::Files); dirinfo.setFile(location() + "/cur"); if (!dirinfo.exists() || !dirinfo.isDir()) { kdDebug(5006) << "Directory " << location() << "/cur doesn't exist or is a file"<< endl; return 1; } QDir curDir(location() + "/cur"); curDir.setFilter(QDir::Files); // then, we look for all the 'cur' files const QFileInfoList *list = curDir.entryInfoList(); QFileInfoListIterator it(*list); QFileInfo *fi; while ((fi = it.current())) { readFileHeaderIntern(curDir.path(), fi->fileName(), KMMsgStatusRead); ++it; } // then, we look for all the 'new' files list = newDir.entryInfoList(); it = *list; while ((fi=it.current())) { readFileHeaderIntern(newDir.path(), fi->fileName(), KMMsgStatusNew); ++it; } if (autoCreateIndex()) { emit statusMsg(i18n("Writing index file")); writeIndex(); } else mHeaderOffset = 0; correctUnreadMsgsCount(); if (kernel->outboxFolder() == this && count() > 0) KMessageBox::information(0, i18n("Your outbox contains messages which were " "most likely not created by KMail.\nPlease remove them from there, if you " "don't want KMail to send them.")); needsCompact = true; parent()->manager()->invalidateFolder(kernel->msgDict(), this); return 0; } KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus() { QFileInfo new_info(location() + "/new"); QFileInfo cur_info(location() + "/cur"); QFileInfo index_info(indexLocation()); if (!index_info.exists()) return KMFolderIndex::IndexMissing; // Check whether the directories are more than 5 seconds newer than the index // file. The 5 seconds are added to reduce the number of false alerts due // to slightly out of sync clocks of the NFS server and the local machine. return ((new_info.lastModified() > index_info.lastModified().addSecs(5)) || (cur_info.lastModified() > index_info.lastModified().addSecs(5))) ? KMFolderIndex::IndexTooOld : KMFolderIndex::IndexOk; } //----------------------------------------------------------------------------- void KMFolderMaildir::removeMsg(int idx, bool) { KMMsgBase* msg = mMsgList[idx]; if (!msg || !msg->fileName()) return; removeFile(msg->fileName()); KMFolderMaildirInherited::removeMsg(idx); } //----------------------------------------------------------------------------- KMMessage* KMFolderMaildir::take(int idx) { // first, we do the high-level stuff.. then delete later KMMessage *msg = KMFolderMaildirInherited::take(idx); if (!msg || !msg->fileName()) return 0; if (removeFile(msg->fileName())) return msg; else return 0; } bool KMFolderMaildir::removeFile(const QString& filename) { // we need to look in both 'new' and 'cur' since it's possible to // delete a message before the folder is compacted. since the file // naming and moving is done in ::compact, we can't assume any // location at this point QString abs_file(location() + "/cur/"); abs_file += filename; if (QFile::exists(abs_file) == false) { abs_file = location() + "/new/"; abs_file += filename; if (QFile::exists(abs_file) == false) { kdDebug(5006) << "Can't delete " << abs_file << " if it doesn't exist!" << endl; return false; } } if(QFile::remove(abs_file) == false) return false; return true; } //----------------------------------------------------------------------------- int KMFolderMaildir::removeContents() { if (KIO::NetAccess::del(KURL::fromPathOrURL(location()))) return 0; return 1; } static QRegExp *suffix_regex = 0; static KStaticDeleter suffix_regex_sd; //----------------------------------------------------------------------------- QString KMFolderMaildir::constructValidFileName(QString& aFileName, KMMsgStatus status) { if (aFileName.isEmpty()) { aFileName.sprintf("%ld.%d.", (long)time(0), getpid()); aFileName += KApplication::randomString(5); } if (!suffix_regex) suffix_regex = suffix_regex_sd.setObject(new QRegExp(":2,?R?S?$")); aFileName.truncate(aFileName.findRev(*suffix_regex)); QString suffix; if ((status != KMMsgStatusNew) && (status != KMMsgStatusUnread)) { suffix += ":2,"; if (status == KMMsgStatusReplied) suffix += "RS"; else suffix += "S"; } aFileName += suffix; return aFileName; } //----------------------------------------------------------------------------- QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, KMMsgInfo *mi) { QString filename(mi->fileName()); QString ret(moveInternal(oldLoc, newLoc, filename, mi->status())); if (filename != mi->fileName()) mi->setFileName(filename); return ret; } //----------------------------------------------------------------------------- QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, QString& aFileName, KMMsgStatus status) { QString dest(newLoc); // make sure that our destination filename doesn't already exist while (QFile::exists(dest)) { aFileName = ""; constructValidFileName(aFileName, status); QFileInfo fi(dest); dest = fi.dirPath(true) + "/" + aFileName; setDirty( true ); } QDir d; if (d.rename(oldLoc, dest) == false) return QString::null; else return dest; } //----------------------------------------------------------------------------- void KMFolderMaildir::msgStatusChanged(const KMMsgStatus oldStatus, const KMMsgStatus newStatus, int idx) { // if the status of any message changes, then we need to compact needsCompact = true; KMFolderMaildirInherited::msgStatusChanged(oldStatus, newStatus, idx); } #include "kmfoldermaildir.moc" diff --git a/kmail/kmfoldermbox.cpp b/kmail/kmfoldermbox.cpp index bdbcdd0166..2b22da4b10 100644 --- a/kmail/kmfoldermbox.cpp +++ b/kmail/kmfoldermbox.cpp @@ -1,1177 +1,1196 @@ // kmfoldermbox.cpp // Author: Stefan Taferner #include #include #include #include #include "kmfoldermbox.h" #include "kmfoldermgr.h" #include "kmmessage.h" #include "kmundostack.h" #include "kbusyptr.h" #include "mboxjob.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_FCNTL_H #include #endif #include #include #include #include #ifndef isblank # define isblank(x) ((x)==' '||(x)=='\t') #endif #ifndef MAX_LINE #define MAX_LINE 4096 #endif #ifndef INIT_MSGS #define INIT_MSGS 8 #endif // Regular expression to find the line that seperates messages in a mail // folder: #define MSG_SEPERATOR_START "From " #define MSG_SEPERATOR_REGEX "^From .*..:...*$" static short msgSepLen = strlen(MSG_SEPERATOR_START); //----------------------------------------------------------------------------- KMFolderMbox::KMFolderMbox(KMFolderDir* aParent, const QString& aName) : KMFolderMboxInherited(aParent, aName) { mStream = 0; mFilesLocked = FALSE; mLockType = None; } //----------------------------------------------------------------------------- KMFolderMbox::~KMFolderMbox() { if (mOpenCount>0) close(TRUE); if (kernel->undoStack()) kernel->undoStack()->folderDestroyed(this); } //----------------------------------------------------------------------------- int KMFolderMbox::open() { int rc = 0; mOpenCount++; if (mOpenCount > 1) return 0; // already open assert(!name().isEmpty()); mFilesLocked = FALSE; mStream = fopen(location().local8Bit(), "r+"); // messages file if (!mStream) { KNotifyClient::event("warning", i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno))); kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl; mOpenCount = 0; return errno; } lock(); if (!path().isEmpty()) { KMFolderIndex::IndexStatus index_status = indexStatus(); // test if index file exists and is up-to-date if (KMFolderIndex::IndexOk != index_status) { // only show a warning if the index file exists, otherwise it can be // silently regenerated if (KMFolderIndex::IndexTooOld == index_status) { QString msg = i18n("

The index of folder '%2' seems " "to be out of date. To prevent message " "corruption the index will be " "regenerated. As a result deleted " "messages might reappear and status " "flags might be lost.

" "

Please read the corresponding entry " "in the FAQ section of the manual " "of KMail for " "information about how to prevent this " "problem from happening again.

") .arg("help:/kmail/faq.html#faq-index-regeneration") .arg(name()); // When KMail is starting up we have to show a non-blocking message // box so that the initialization can continue. We don't show a // queued message box when KMail isn't starting up because queued // message boxes don't have a "Don't ask again" checkbox. if (kernel->startingUp()) { KConfigGroup configGroup( KMKernel::config(), "Notification Messages" ); bool showMessage = configGroup.readBoolEntry( "showIndexRegenerationMessage", true ); if (showMessage) KMessageBox::queuedMessageBox( 0, KMessageBox::Information, msg, i18n("Index Out of Date"), KMessageBox::AllowLink ); } else { bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::information( 0, msg, i18n("Index Out of Date"), "showIndexRegenerationMessage", KMessageBox::AllowLink ); if (busy) kernel->kbp()->busy(); } // ######### FIXME-AFTER-MSG-FREEZE: Delete this after the msg freeze if( 0 ) { KMessageBox::information( 0, i18n("

The index of folder '%1' seems " "to be out of date. To prevent message " "corruption the index will be " "regenerated. As a result deleted " "messages might reappear and status " "flags might be lost.

" "

Please read the corresponding entry " "in the FAQ section of the manual of " "KMail for " "information about how to prevent this " "problem from happening again.

") .arg(name()), i18n("Index Out of Date"), "dontshowIndexRegenerationWarning"); } // ######### end of FIXME-AFTER-MSG-FREEZE: Delete this after the msg freeze } QString str; mIndexStream = 0; str = i18n("Folder `%1' changed. Recreating index.") .arg(name()); emit statusMsg(str); } else { mIndexStream = fopen(indexLocation().local8Bit(), "r+"); // index file updateIndexStreamPtr(); } if (!mIndexStream) rc = createIndexFromContents(); else if (!readIndex()) rc = createIndexFromContents(); } else { mAutoCreateIndex = FALSE; rc = createIndexFromContents(); } mQuiet = 0; mChanged = FALSE; return rc; } //---------------------------------------------------------------------------- int KMFolderMbox::canAccess() { assert(!name().isEmpty()); if (access(location().local8Bit(), R_OK | W_OK) != 0) { kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl; return 1; } return 0; } //----------------------------------------------------------------------------- int KMFolderMbox::create(bool imap) { int rc; int old_umask; Q_UNUSED(imap); assert(!name().isEmpty()); assert(mOpenCount == 0); kdDebug(5006) << "Creating folder " << name() << endl; if (access(location().local8Bit(), F_OK) == 0) { kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl; kdDebug(5006) << "File:: " << endl; kdDebug(5006) << "Error " << endl; return EEXIST; } old_umask = umask(077); mStream = fopen(location().local8Bit(), "w+"); //sven; open RW umask(old_umask); if (!mStream) return errno; if (!path().isEmpty()) { old_umask = umask(077); mIndexStream = fopen(indexLocation().local8Bit(), "w+"); //sven; open RW updateIndexStreamPtr(TRUE); umask(old_umask); if (!mIndexStream) return errno; } else { mAutoCreateIndex = FALSE; } mOpenCount++; mQuiet = 0; mChanged = FALSE; rc = writeIndex(); if (!rc) lock(); return rc; } //----------------------------------------------------------------------------- void KMFolderMbox::close(bool aForced) { if (mOpenCount <= 0 || !mStream) return; if (mOpenCount > 0) mOpenCount--; if (mOpenCount > 0 && !aForced) return; if ((this != kernel->inboxFolder()) && isSystemFolder() && !aForced) return; if (mAutoCreateIndex) { if (KMFolderIndex::IndexOk != indexStatus()) { kdDebug(5006) << "Critical error: " << location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } updateIndex(); writeConfig(); } unlock(); mMsgList.clear(TRUE); if (mStream) fclose(mStream); if (mIndexStream) { fclose(mIndexStream); updateIndexStreamPtr(TRUE); } mOpenCount = 0; mStream = 0; mIndexStream = 0; mFilesLocked = FALSE; mUnreadMsgs = -1; mMsgList.reset(INIT_MSGS); } //----------------------------------------------------------------------------- void KMFolderMbox::sync() { if (mOpenCount > 0) if (!mStream || fsync(fileno(mStream)) || !mIndexStream || fsync(fileno(mIndexStream))) { kernel->emergencyExit( i18n("Not enough free disk space." )); } } //----------------------------------------------------------------------------- int KMFolderMbox::lock() { int rc; struct flock fl; fl.l_type=F_WRLCK; fl.l_whence=0; fl.l_start=0; fl.l_len=0; fl.l_pid=-1; QCString cmd_str; assert(mStream != 0); mFilesLocked = FALSE; switch( mLockType ) { case FCNTL: rc = fcntl(fileno(mStream), F_SETLKW, &fl); if (rc < 0) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(errno) << " (" << errno << ")" << endl; return errno; } if (mIndexStream) { rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); if (rc < 0) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(errno) << " (" << errno << ")" << endl; rc = errno; fl.l_type = F_UNLCK; rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); return rc; } } break; case procmail_lockfile: cmd_str = "lockfile -l20 -r5 "; if (!mProcmailLockFileName.isEmpty()) cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); else cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } if( mIndexStream ) { cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } } break; case mutt_dotlock: cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } if( mIndexStream ) { cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } } break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } if( mIndexStream ) { cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } } break; case None: default: break; } mFilesLocked = TRUE; return 0; } //------------------------------------------------------------- FolderJob* KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder ) const { MboxJob *job = new MboxJob( msg, jt, folder ); job->setParent( this ); return job; } //------------------------------------------------------------- FolderJob* KMFolderMbox::doCreateJob( QPtrList& msgList, const QString& sets, FolderJob::JobType jt, KMFolder *folder ) const { MboxJob *job = new MboxJob( msgList, sets, jt, folder ); job->setParent( this ); return job; } //----------------------------------------------------------------------------- int KMFolderMbox::unlock() { int rc; struct flock fl; fl.l_type=F_UNLCK; fl.l_whence=0; fl.l_start=0; fl.l_len=0; QCString cmd_str; assert(mStream != 0); mFilesLocked = FALSE; switch( mLockType ) { case FCNTL: if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl); fcntl(fileno(mStream), F_SETLK, F_UNLCK); rc = errno; break; case procmail_lockfile: cmd_str = "rm -f "; if (!mProcmailLockFileName.isEmpty()) cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); else cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "rm -f " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); rc = system( cmd_str.data() ); } break; case mutt_dotlock: cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); } break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); } break; case None: default: rc = 0; break; } return rc; } //----------------------------------------------------------------------------- KMFolderIndex::IndexStatus KMFolderMbox::indexStatus() { QFileInfo contInfo(location()); QFileInfo indInfo(indexLocation()); if (!contInfo.exists()) return KMFolderIndex::IndexOk; if (!indInfo.exists()) return KMFolderIndex::IndexMissing; // Check whether the mbox file is more than 5 seconds newer than the index // file. The 5 seconds are added to reduce the number of false alerts due // to slightly out of sync clocks of the NFS server and the local machine. return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) ) ? KMFolderIndex::IndexTooOld : KMFolderIndex::IndexOk; } //----------------------------------------------------------------------------- int KMFolderMbox::createIndexFromContents() { char line[MAX_LINE]; char status[8], xstatus[8]; QCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0; - QCString replyToIdStr, referencesStr, msgIdStr; + QCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr; bool atEof = FALSE; bool inHeader = TRUE; KMMsgInfo* mi; QString msgStr; QRegExp regexp(MSG_SEPERATOR_REGEX); int i, num, numStatus; short needStatus; quiet(TRUE); assert(mStream != 0); rewind(mStream); mMsgList.clear(); num = -1; numStatus= 11; off_t offs = 0; size_t size = 0; dateStr = ""; fromStr = ""; toStr = ""; subjStr = ""; *status = '\0'; *xstatus = '\0'; xmarkStr = ""; replyToIdStr = ""; + replyToAuxIdStr = ""; + referencesStr = ""; msgIdStr = ""; needStatus = 3; while (!atEof) { off_t pos = ftell(mStream); if (!fgets(line, MAX_LINE, mStream)) atEof = TRUE; if (atEof || (strncmp(line,MSG_SEPERATOR_START, msgSepLen)==0 && regexp.search(line) >= 0)) { size = pos - offs; pos = ftell(mStream); if (num >= 0) { if (numStatus <= 0) { msgStr = i18n("Creating index file: %n message done", "Creating index file: %n messages done", num); emit statusMsg(msgStr); numStatus = 10; } if (size > 0) { if ((replyToIdStr.isEmpty() || (replyToIdStr[0] != '<')) && !referencesStr.isEmpty() && referencesStr[0] == '<') { - replyToIdStr = referencesStr; + // use the last reference, instead of missing In-Reply-To + int leftAngle = referencesStr.findRev( '<' ); + if (leftAngle != -1) + replyToIdStr = referencesStr.mid(leftAngle); } + mi = new KMMsgInfo(this); - mi->init(subjStr, fromStr, toStr, 0, KMMsgStatusNew, xmarkStr, replyToIdStr, msgIdStr, - KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, + mi->init(subjStr, fromStr, toStr, 0, KMMsgStatusNew, xmarkStr, + replyToIdStr, replyToAuxIdStr, msgIdStr, + KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, KMMsgMDNStateUnknown, offs, size); mi->setStatus("RO","O"); mi->setDate(dateStr); mi->setDirty(FALSE); mMsgList.append(mi); *status = '\0'; *xstatus = '\0'; needStatus = 3; xmarkStr = ""; replyToIdStr = ""; + replyToAuxIdStr = ""; referencesStr = ""; msgIdStr = ""; dateStr = ""; fromStr = ""; subjStr = ""; } else num--,numStatus++; } offs = ftell(mStream); num++; numStatus--; inHeader = TRUE; continue; } // Is this a long header line? if (inHeader && (line[0]=='\t' || line[0]==' ')) { i = 0; while (line [i]=='\t' || line [i]==' ') i++; if (line [i] < ' ' && line [i]>0) inHeader = FALSE; else if (lastStr) *lastStr += line + i; } else lastStr = 0; if (inHeader && (line [0]=='\n' || line [0]=='\r')) inHeader = FALSE; if (!inHeader) continue; /* -sanders Make all messages read when auto-recreating index if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0 && isblank(line[7])) { for(i=0; i<4 && line[i+8] > ' '; i++) status[i] = line[i+8]; status[i] = '\0'; needStatus &= ~1; } else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0 && isblank(line[9])) { for(i=0; i<4 && line[i+10] > ' '; i++) xstatus[i] = line[i+10]; xstatus[i] = '\0'; needStatus &= ~2; } else*/ if (strncasecmp(line,"X-KMail-Mark:",13)==0 && isblank(line[13])) xmarkStr = QCString(line+14); else if (strncasecmp(line,"In-Reply-To:",12)==0 && isblank(line[12])) { int rightAngle; replyToIdStr = QCString(line+13); rightAngle = replyToIdStr.find( '>' ); if (rightAngle != -1) replyToIdStr.truncate( rightAngle + 1 ); } else if (strncasecmp(line,"References:",11)==0 && isblank(line[11])) { int leftAngle, rightAngle; referencesStr = QCString(line+12); leftAngle = referencesStr.findRev( '<' ); + leftAngle = referencesStr.findRev( '<', leftAngle-1); if (leftAngle != -1) referencesStr = referencesStr.mid( leftAngle ); - rightAngle = referencesStr.find( '>' ); + rightAngle = referencesStr.findRev( '>' ); if (rightAngle != -1) referencesStr.truncate( rightAngle + 1 ); + + // Store the second to last reference in the replyToAuxIdStr + // It is a good candidate for threading the message below if the + // message In-Reply-To points to is not kept in this folder, + // but e.g. in an Outbox + replyToAuxIdStr = referencesStr; + rightAngle = referencesStr.find( '>' ); + if (rightAngle != -1) + replyToAuxIdStr.truncate( rightAngle + 1 ); + } else if (strncasecmp(line,"Message-Id:",11)==0 && isblank(line[11])) { int rightAngle; msgIdStr = QCString(line+12); rightAngle = msgIdStr.find( '>' ); if (rightAngle != -1) msgIdStr.truncate( rightAngle + 1 ); } else if (strncasecmp(line,"Date:",5)==0 && isblank(line[5])) { dateStr = QCString(line+6); lastStr = &dateStr; } else if (strncasecmp(line,"From:", 5)==0 && isblank(line[5])) { fromStr = QCString(line+6); lastStr = &fromStr; } else if (strncasecmp(line,"To:", 3)==0 && isblank(line[3])) { toStr = QCString(line+4); lastStr = &toStr; } else if (strncasecmp(line,"Subject:",8)==0 && isblank(line[8])) { subjStr = QCString(line+9); lastStr = &subjStr; } } if (mAutoCreateIndex) { emit statusMsg(i18n("Writing index file")); writeIndex(); } else mHeaderOffset = 0; quiet(FALSE); correctUnreadMsgsCount(); if (kernel->outboxFolder() == this && count() > 0) KMessageBox::queuedMessageBox(0, KMessageBox::Information, i18n("Your outbox contains messages which were " "most likely not created by KMail.\nPlease remove them from there, if you " "don't want KMail to send them.")); if ( parent() ) parent()->manager()->invalidateFolder(kernel->msgDict(), this); return 0; } //----------------------------------------------------------------------------- KMMessage* KMFolderMbox::readMsg(int idx) { KMMessage* msg; unsigned long msgSize; QCString msgText; KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0 && !mi->isMessage()); assert(mStream != 0); msgSize = mi->msgSize(); msgText.resize(msgSize+2); fseek(mStream, mi->folderOffset(), SEEK_SET); fread(msgText.data(), msgSize, 1, mStream); msgText[msgSize] = '\0'; msg = new KMMessage(*mi); msg->fromString(msgText); mMsgList.set(idx,msg); return msg; } //----------------------------------------------------------------------------- QCString& KMFolderMbox::getMsgString(int idx, QCString &mDest) { unsigned long msgSize; KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0); assert(mStream != 0); msgSize = mi->msgSize(); mDest.resize(msgSize+2); fseek(mStream, mi->folderOffset(), SEEK_SET); fread(mDest.data(), msgSize, 1, mStream); mDest[msgSize] = '\0'; return mDest; } //----------------------------------------------------------------------------- DwString KMFolderMbox::getDwString(int idx) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0); assert(mStream != 0); fseek(mStream, mi->folderOffset(), SEEK_SET); return DwString(mStream, mi->msgSize()); } //----------------------------------------------------------------------------- int KMFolderMbox::addMsg(KMMessage* aMsg, int* aIndex_ret) { if (!canAddMsgNow(aMsg, aIndex_ret)) return 0; bool opened = FALSE; QCString msgText; char endStr[3]; int idx = -1, rc; KMFolder* msgParent; bool editing = false; int growth = 0; /* Then we can also disable it completely, this wastes time, at least for IMAP if (KMFolder::IndexOk != indexStatus()) { kdDebug(5006) << "Critical error: " << location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } */ if (!mStream) { opened = TRUE; rc = open(); kdDebug(5006) << "addMsg-open: " << rc << endl; if (rc) return rc; } // take message out of the folder it is currently in, if any msgParent = aMsg->parent(); if (msgParent) { if (msgParent==this) { if (kernel->folderIsDraftOrOutbox(this)) //special case for Edit message. { kdDebug(5006) << "Editing message in outbox or drafts" << endl; editing = true; } else return 0; } idx = msgParent->find(aMsg); msgParent->getMsg( idx ); } if (protocol() != "imap") { /* QFile fileD0( "testdat_xx-kmfoldermbox-0" ); if( fileD0.open( IO_WriteOnly ) ) { QDataStream ds( &fileD0 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD0.close(); // If data is 0 we just create a zero length file. } */ aMsg->setStatusFields(); /* QFile fileD1( "testdat_xx-kmfoldermbox-1" ); if( fileD1.open( IO_WriteOnly ) ) { QDataStream ds( &fileD1 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD1.close(); // If data is 0 we just create a zero length file. } */ if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by aMsg->removeHeaderField("Content-Type"); // the line above } msgText = aMsg->asString(); msgText.replace("\nFrom ", "\n>From "); size_t len = msgText.length(); assert(mStream != 0); clearerr(mStream); if (len <= 0) { kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl; if (opened) close(); return 0; } // Make sure the file is large enough to check for an end // character fseek(mStream, 0, SEEK_END); off_t revert = ftell(mStream); if (ftell(mStream) >= 2) { // write message to folder file fseek(mStream, -2, SEEK_END); fread(endStr, 1, 2, mStream); // ensure separating empty line if (ftell(mStream) > 0 && endStr[0]!='\n') { ++growth; if (endStr[1]!='\n') { //printf ("****endStr[1]=%c\n", endStr[1]); fwrite("\n\n", 1, 2, mStream); ++growth; } else fwrite("\n", 1, 1, mStream); } } fseek(mStream,0,SEEK_END); // this is needed on solaris and others int error = ferror(mStream); if (error) { if (opened) close(); return error; } fprintf(mStream, "From %s %s\n", (const char *)aMsg->fromEmail(), (const char *)aMsg->dateShortStr()); off_t offs = ftell(mStream); fwrite(msgText, len, 1, mStream); if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream); fflush(mStream); size_t size = ftell(mStream) - offs; error = ferror(mStream); if (error) { kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; if (ftell(mStream) > revert) { kdDebug(5006) << "Undoing changes" << endl; truncate( location().local8Bit(), revert ); } kernel->emergencyExit( i18n("Not enough free disk space.") ); /* This code is not 100% reliable bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Unable to add message to folder.\n" "(No space left on device or insufficient quota?)\n" "Free space and sufficient quota are required to continue safely.")); if (busy) kernel->kbp()->busy(); if (opened) close(); kernel->kbp()->idle(); */ return error; } if (msgParent) { if (idx >= 0) msgParent->take(idx); } // if (mAccount) aMsg->removeHeaderField("X-UID"); if (aMsg->status()==KMMsgStatusUnread || aMsg->status()==KMMsgStatusNew || (this == kernel->outboxFolder())) { if (mUnreadMsgs == -1) mUnreadMsgs = 1; else ++mUnreadMsgs; emit numUnreadMsgsChanged( this ); } ++mTotalMsgs; // store information about the position in the folder file in the message aMsg->setParent(this); aMsg->setFolderOffset(offs); aMsg->setMsgSize(size); idx = mMsgList.append(aMsg); if (aMsg->getMsgSerNum() <= 0) aMsg->setMsgSerNum(); // change the length of the previous message to encompass white space added if ((idx > 0) && (growth > 0)) { // don't grow if a deleted message claims space at the end of the file if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() ) mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth ); } // write index entry if desired if (mAutoCreateIndex) { assert(mIndexStream != 0); clearerr(mIndexStream); fseek(mIndexStream, 0, SEEK_END); revert = ftell(mIndexStream); int len; const uchar *buffer = aMsg->asIndexString(len); fwrite(&len,sizeof(len), 1, mIndexStream); aMsg->setIndexOffset( ftell(mIndexStream) ); aMsg->setIndexLength( len ); if(fwrite(buffer, len, 1, mIndexStream) != 1) kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; fflush(mIndexStream); error = ferror(mIndexStream); error |= appendtoMsgDict(idx); if (error) { kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; if (ftell(mIndexStream) > revert) { kdWarning(5006) << "Undoing changes" << endl; truncate( indexLocation().local8Bit(), revert ); } kernel->emergencyExit( i18n("Not enough free disk space.") ); /* This code may not be 100% reliable bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Unable to add message to folder.\n" "(No space left on device or insufficient quota?)\n" "Free space and sufficient quota are required to continue safely.")); if (busy) kernel->kbp()->busy(); if (opened) close(); */ return error; } } // some "paper work" if (aIndex_ret) *aIndex_ret = idx; emitMsgAddedSignals(idx); if (opened) close(); // All streams have been flushed without errors if we arrive here // Return success! // (Don't return status of stream, it may have been closed already.) return 0; } //----------------------------------------------------------------------------- int KMFolderMbox::compact() { QString tempName; QString msgStr; int rc = 0; int openCount = mOpenCount; if (!needsCompact) return 0; if (!mCompactable) { kdDebug(5006) << location() << " compaction skipped." << endl; return 0; } kdDebug(5006) << "Compacting " << idString() << endl; if (KMFolderIndex::IndexOk != indexStatus()) { kdDebug(5006) << "Critical error: " << location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } tempName = path() + "/." + name() + ".compacted"; mode_t old_umask = umask(077); FILE *tmpfile = fopen(tempName.local8Bit(), "w"); umask(old_umask); if (!tmpfile) return errno; open(); KMMsgInfo* mi; size_t msize; off_t folder_offset; off_t offs=0; size_t msgs=0; QCString mtext; for(int idx = 0; idx < mMsgList.count(); idx++) { if(!(msgs++ % 10)) { msgStr = i18n("Compacting folder: %1 messages done").arg(msgs); emit statusMsg(msgStr); } mi = (KMMsgInfo*)mMsgList[idx]; msize = mi->msgSize(); if (mtext.size() < msize + 2) mtext.resize(msize+2); folder_offset = mi->folderOffset(); //now we need to find the separator! grr... for(off_t i = folder_offset-25; TRUE; i -= 20) { off_t chunk_offset = i <= 0 ? 0 : i; if(fseek(mStream, chunk_offset, SEEK_SET) == -1) { rc = errno; break; } if (mtext.size() < 20) mtext.resize(20); fread(mtext.data(), 20, 1, mStream); if(i <= 0) { //woops we've reached the top of the file, last try.. if(!strncasecmp(mtext.data(), "from ", 5)) { if (mtext.size() < (size_t)folder_offset) mtext.resize(folder_offset); if(fseek(mStream, chunk_offset, SEEK_SET) == -1 || !fread(mtext.data(), folder_offset, 1, mStream) || !fwrite(mtext.data(), folder_offset, 1, tmpfile)) { rc = errno; break; } offs += folder_offset; } else { rc = 666; } break; } else { int last_crlf = -1; for(int i2 = 0; i2 < 20; i2++) { if(*(mtext.data()+i2) == '\n') last_crlf = i2; } if(last_crlf != -1) { int size = folder_offset - (i + last_crlf+1); if ((int)mtext.size() < size) mtext.resize(size); if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 || !fread(mtext.data(), size, 1, mStream) || !fwrite(mtext.data(), size, 1, tmpfile)) { rc = errno; break; } offs += size; break; } } } if (rc) break; //now actually write the message if(fseek(mStream, folder_offset, SEEK_SET) == -1 || !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) { rc = errno; break; } mi->setFolderOffset(offs); offs += msize; } if (!rc) rc = fflush(tmpfile); if (!rc) rc = fsync(fileno(tmpfile)); rc |= fclose(tmpfile); if (!rc) { bool autoCreate = mAutoCreateIndex; ::rename(tempName.local8Bit(), location().local8Bit()); writeIndex(); writeConfig(); mAutoCreateIndex = false; close(TRUE); mAutoCreateIndex = autoCreate; } else { close(); kdDebug(5006) << "Error occurred while compacting" << endl; kdDebug(5006) << location() << endl; kdDebug(5006) << "Compaction aborted." << endl; } if (openCount > 0) { open(); mOpenCount = openCount; } quiet(FALSE); if (!mQuiet) emit changed(); else mChanged = TRUE; needsCompact = false; // We are clean now return 0; } //----------------------------------------------------------------------------- void KMFolderMbox::setLockType( LockType ltype ) { mLockType = ltype; } //----------------------------------------------------------------------------- void KMFolderMbox::setProcmailLockFileName( const QString &fname ) { mProcmailLockFileName = fname; } //----------------------------------------------------------------------------- int KMFolderMbox::removeContents() { int rc = 0; rc = unlink(location().local8Bit()); return rc; } //----------------------------------------------------------------------------- int KMFolderMbox::expungeContents() { int rc = 0; if (truncate(location().local8Bit(), 0)) rc = errno; return rc; } //----------------------------------------------------------------------------- #include "kmfoldermbox.moc" diff --git a/kmail/kmheaders.cpp b/kmail/kmheaders.cpp index ee2ddea293..889376e2fd 100644 --- a/kmail/kmheaders.cpp +++ b/kmail/kmheaders.cpp @@ -1,2920 +1,3016 @@ // kmheaders.cpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kmcommands.h" #include "kmfolderimap.h" #include "kmfoldermgr.h" #include "kmheaders.h" #include "kbusyptr.h" #include "kmmainwidget.h" #include "kmcomposewin.h" #include "kmfiltermgr.h" #include "kmsender.h" #include "kmundostack.h" #include "kmmsgdict.h" #include "folderjob.h" using KMail::FolderJob; #include "mailinglist-magic.h" #include "kmbroadcaststatus.h" #include #include #include #include #if 0 //timing utilities #include #define CREATE_TIMER(x) int x=0, x ## _tmp=0; QTime x ## _tmp2 #define START_TIMER(x) x ## _tmp2 = QTime::currentTime() #define GRAB_TIMER(x) x ## _tmp2.msecsTo(QTime::currentTime()) #define END_TIMER(x) x += GRAB_TIMER(x); x ## _tmp++ #define SHOW_TIMER(x) qDebug(#x " == %d (%d)", x, x ## _tmp) #else #define CREATE_TIMER(x) #define START_TIMER(x) #define GRAB_TIMER(x) #define END_TIMER(x) #define SHOW_TIMER(x) #endif QPixmap* KMHeaders::pixNew = 0; QPixmap* KMHeaders::pixUns = 0; QPixmap* KMHeaders::pixDel = 0; QPixmap* KMHeaders::pixOld = 0; QPixmap* KMHeaders::pixRep = 0; QPixmap* KMHeaders::pixQueued = 0; QPixmap* KMHeaders::pixSent = 0; QPixmap* KMHeaders::pixFwd = 0; QPixmap* KMHeaders::pixFlag = 0; QPixmap* KMHeaders::pixFullySigned = 0; QPixmap* KMHeaders::pixPartiallySigned = 0; QPixmap* KMHeaders::pixUndefinedSigned = 0; QPixmap* KMHeaders::pixFullyEncrypted = 0; QPixmap* KMHeaders::pixPartiallyEncrypted = 0; QPixmap* KMHeaders::pixUndefinedEncrypted = 0; QPixmap* KMHeaders::pixFiller = 0; QPixmap* KMHeaders::pixEncryptionProblematic = 0; QPixmap* KMHeaders::pixSignatureProblematic = 0; bool KMHeaders::mTrue = true; bool KMHeaders::mFalse = false; //----------------------------------------------------------------------------- // KMHeaderItem method definitions class KMHeaderItem : public KListViewItem { public: int mMsgId; QString mKey; // WARNING: Do not add new member variables to the class // Constuction a new list view item with the given colors and pixmap KMHeaderItem( QListView* parent, int msgId, QString key = QString::null) : KListViewItem( parent ), mMsgId( msgId ), mKey(key) { irefresh(); } // Constuction a new list view item with the given parent, colors, & pixmap KMHeaderItem( QListViewItem* parent, int msgId, QString key = QString::null) : KListViewItem( parent ), mMsgId( msgId ), mKey(key) { irefresh(); } // Update the msgId this item corresponds to. void setMsgId( int aMsgId ) { mMsgId = aMsgId; } // Profiling note: About 30% of the time taken to initialize the // listview is spent in this function. About 60% is spent in operator // new and QListViewItem::QListViewItem. void irefresh() { KMHeaders *headers = static_cast(listView()); NestingPolicy threadingPolicy = headers->getNestingPolicy(); if ((threadingPolicy == AlwaysOpen) || (threadingPolicy == DefaultOpen)) { //Avoid opening items as QListView is currently slow to do so. if (parent()) parent()->setOpen(true); return; } if (threadingPolicy == DefaultClosed) return; //default to closed // otherwise threadingPolicy == OpenUnread if (parent() && parent()->isOpen()) { setOpen(true); return; } KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); if (mMsgBase->status() == KMMsgStatusNew || mMsgBase->status() == KMMsgStatusUnread || mMsgBase->status() == KMMsgStatusFlag) { setOpen(true); KMHeaderItem * topOfThread = this; while(topOfThread->parent()) topOfThread = (KMHeaderItem*)topOfThread->parent(); topOfThread->setOpenRecursive(true); } } // Return the msgId of the message associated with this item int msgId() { return mMsgId; } // Update this item to summarise a new folder and message void reset( int aMsgId ) { mMsgId = aMsgId; irefresh(); } //Opens all children in the thread void setOpenRecursive( bool open ) { if (open){ QListViewItem * lvchild; lvchild = firstChild(); while (lvchild){ ((KMHeaderItem*)lvchild)->setOpenRecursive( true ); lvchild = lvchild->nextSibling(); } setOpen( true ); } else { setOpen( false ); } } QString text( int col) const { KMHeaders *headers = static_cast(listView()); KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); QString tmp; if(col == headers->paintInfo()->flagCol) { if (headers->paintInfo()->flagCol >= 0) tmp = QString( QChar( (char)mMsgBase->status() )); } else if(col == headers->paintInfo()->senderCol) { if (headers->folder()->whoField().lower() == "to") tmp = mMsgBase->toStrip(); else tmp = mMsgBase->fromStrip(); if (tmp.isEmpty()) tmp = i18n("Unknown"); else tmp = tmp.simplifyWhiteSpace(); } else if(col == headers->paintInfo()->subCol) { tmp = mMsgBase->subject(); if (tmp.isEmpty()) tmp = i18n("No Subject"); else tmp = tmp.simplifyWhiteSpace(); } else if(col == headers->paintInfo()->dateCol) { tmp = headers->mDate.dateString( mMsgBase->date() ); } else if(col == headers->paintInfo()->sizeCol && headers->paintInfo()->showSize) { if (headers->folder()->protocol() == "imap") { QCString cstr; headers->folder()->getMsgString(mMsgId, cstr); int a = cstr.find("\nX-Length: "); if(a != -1) { int b = cstr.find('\n', a+11); tmp = KIO::convertSize(cstr.mid(a+11, b-a-11).toULong()); } } else tmp = KIO::convertSize(mMsgBase->msgSize()); } return tmp; } void setup() { widthChanged(); const int ph = KMHeaders::pixNew->height(); QListView *v = listView(); int h = QMAX( v->fontMetrics().height(), ph ) + 2*v->itemMargin(); h = QMAX( h, QApplication::globalStrut().height()); if ( h % 2 > 0 ) h++; setHeight( h ); } typedef QValueList PixmapList; QPixmap pixmapMerge( PixmapList pixmaps ) const { int width = 0; int height = 0; for ( PixmapList::ConstIterator it = pixmaps.begin(); it != pixmaps.end(); ++it ) { width += (*it).width(); height = QMAX( height, (*it).height() ); } QPixmap res( width, height ); QBitmap mask( width, height ); int x = 0; for ( PixmapList::ConstIterator it = pixmaps.begin(); it != pixmaps.end(); ++it ) { bitBlt( &res, x, 0, &(*it) ); bitBlt( &mask, x, 0, (*it).mask() ); x += (*it).width(); } res.setMask( mask ); return res; } const QPixmap * pixmap( int col) const { if(!col) { KMHeaders *headers = static_cast(listView()); KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); PixmapList pixmaps; switch (mMsgBase->status()) { case KMMsgStatusNew: pixmaps << *KMHeaders::pixNew; break; case KMMsgStatusUnread: pixmaps << *KMHeaders::pixUns; break; case KMMsgStatusDeleted: pixmaps << *KMHeaders::pixDel; break; case KMMsgStatusReplied: pixmaps << *KMHeaders::pixRep; break; case KMMsgStatusForwarded: pixmaps << *KMHeaders::pixFwd; break; case KMMsgStatusQueued: pixmaps << *KMHeaders::pixQueued; break; case KMMsgStatusSent: pixmaps << *KMHeaders::pixSent; break; case KMMsgStatusFlag: pixmaps << *KMHeaders::pixFlag; break; default: pixmaps << *KMHeaders::pixOld; break; } // Only merge the crypto icons in if that is configured. if( headers->paintInfo()->showCryptoIcons ) { if( mMsgBase->encryptionState() == KMMsgFullyEncrypted ) pixmaps << *KMHeaders::pixFullyEncrypted; else if( mMsgBase->encryptionState() == KMMsgPartiallyEncrypted ) pixmaps << *KMHeaders::pixPartiallyEncrypted; else if( mMsgBase->encryptionState() == KMMsgEncryptionStateUnknown ) pixmaps << *KMHeaders::pixUndefinedEncrypted; else if( mMsgBase->encryptionState() == KMMsgEncryptionProblematic ) pixmaps << *KMHeaders::pixEncryptionProblematic; else pixmaps << *KMHeaders::pixFiller; if( mMsgBase->signatureState() == KMMsgFullySigned ) pixmaps << *KMHeaders::pixFullySigned; else if( mMsgBase->signatureState() == KMMsgPartiallySigned ) pixmaps << *KMHeaders::pixPartiallySigned; else if( mMsgBase->signatureState() == KMMsgSignatureStateUnknown ) pixmaps << *KMHeaders::pixUndefinedSigned; else if( mMsgBase->signatureState() == KMMsgSignatureProblematic ) pixmaps << *KMHeaders::pixSignatureProblematic; else pixmaps << *KMHeaders::pixFiller; } static QPixmap mergedpix; mergedpix = pixmapMerge( pixmaps ); return &mergedpix; } return 0; } void paintCell( QPainter * p, const QColorGroup & cg, int column, int width, int align ) { KMHeaders *headers = static_cast(listView()); if (headers->noRepaint) return; if (!headers->folder()) return; QColorGroup _cg( cg ); QColor c = _cg.text(); QColor *color; KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId ); if (!mMsgBase) return; switch (mMsgBase->status()) { case KMMsgStatusNew: color = (QColor*)(&headers->paintInfo()->colNew); break; case KMMsgStatusUnread: color = (QColor*)(&headers->paintInfo()->colUnread); break; case KMMsgStatusFlag: color = (QColor *)(&headers->paintInfo()->colFlag); break; default: color = (QColor *)(&headers->paintInfo()->colFore); break; } _cg.setColor( QColorGroup::Text, *color ); if( column == headers->paintInfo()->dateCol ) p->setFont(headers->dateFont); KListViewItem::paintCell( p, _cg, column, width, align ); _cg.setColor( QColorGroup::Text, c ); } static QString generate_key( int id, KMHeaders *headers, KMMsgBase *msg, const KPaintInfo *paintInfo, int sortOrder) { // It appears, that QListView in Qt-3.0 asks for the key // in QListView::clear(), which is called from // readSortOrder() if (!msg) return QString::null; int column = sortOrder & ((1 << 5) - 1); QString ret = QChar( (char)sortOrder ); QString sortArrival = QString( "%1" ) .arg( kernel->msgDict()->getMsgSerNum(headers->folder(), id), 0, 36 ); while (sortArrival.length() < 7) sortArrival = '0' + sortArrival; if (column == paintInfo->dateCol) { if (paintInfo->orderOfArrival) return ret + sortArrival; else { static const int dateLength = 30; char cDate[dateLength + 1]; const time_t date = msg->date(); strftime( cDate, dateLength, "%Y:%j:%H:%M:%S", gmtime( &date )); return ret + cDate + sortArrival; } } else if (column == paintInfo->senderCol) { QString tmp; if (headers->folder()->whoField().lower() == "to") tmp = msg->toStrip(); else tmp = msg->fromStrip(); return ret + tmp.lower() + ' ' + sortArrival; } else if (column == paintInfo->subCol) { if (paintInfo->status) return ret + QString( QChar( (uint)msg->status() )) + sortArrival; return ret + KMMessage::stripOffPrefixes( msg->subject().lower() ) + ' ' + sortArrival; } else if (column == paintInfo->sizeCol) { QString len; if (headers->folder()->protocol() == "imap") { QCString cstr; headers->folder()->getMsgString(id, cstr); int a = cstr.find("\nX-Length: "); int b = cstr.find('\n', a+1); len = QString( "%1" ).arg( cstr.mid(a+11, b-a-11) ); } else { len = QString( "%1" ).arg( msg->msgSize() ); } while (len.length() < 9) len = '0' + len; return ret + len; } return ret + "missing key"; //you forgot something!! } virtual QString key( int column, bool /*ascending*/ ) const { KMHeaders *headers = static_cast(listView()); int sortOrder = column; if (headers->mPaintInfo.orderOfArrival) sortOrder |= (1 << 6); if (headers->mPaintInfo.status) sortOrder |= (1 << 5); //This code should stay pretty much like this, if you are adding new //columns put them in generate_key if(mKey.isEmpty() || mKey[0] != (char)sortOrder) { KMHeaders *headers = static_cast(listView()); return ((KMHeaderItem *)this)->mKey = generate_key(mMsgId, headers, headers->folder()->getMsgBase( mMsgId ), headers->paintInfo(), sortOrder); } return mKey; } void setTempKey( QString key ) { mKey = key; } QListViewItem* firstChildNonConst() /* Non const! */ { enforceSortOrder(); // Try not to rely on QListView implementation details return firstChild(); } }; //----------------------------------------------------------------------------- KMHeaders::KMHeaders(KMMainWidget *aOwner, QWidget *parent, const char *name) : KMHeadersInherited(parent, name) { static bool pixmapsLoaded = FALSE; //qInitImageIO(); KImageIO::registerFormats(); mOwner = aOwner; mFolder = 0; noRepaint = FALSE; getMsgIndex = -1; mTopItem = 0; setMultiSelection( TRUE ); setAllColumnsShowFocus( TRUE ); mNested = false; nestingPolicy = OpenUnread; mNestedOverride = false; mousePressed = FALSE; mSortInfo.dirty = TRUE; mSortInfo.fakeSort = 0; mSortInfo.removed = 0; mSortInfo.column = 0; mSortInfo.ascending = false; mJumpToUnread = false; setLineWidth(0); // popup-menu header()->setClickEnabled(true); header()->installEventFilter(this); mPopup = new KPopupMenu(this); mPopup->insertTitle(i18n("View columns")); mPopup->setCheckable(true); mSizeColumn = mPopup->insertItem(i18n("Size Column"), this, SLOT(slotToggleSizeColumn())); mPaintInfo.flagCol = -1; mPaintInfo.subCol = mPaintInfo.flagCol + 1; mPaintInfo.senderCol = mPaintInfo.subCol + 1; mPaintInfo.dateCol = mPaintInfo.senderCol + 1; mPaintInfo.sizeCol = mPaintInfo.dateCol + 1; mPaintInfo.orderOfArrival = false; mPaintInfo.status = false; mSortCol = KMMsgList::sfDate; mSortDescending = FALSE; readConfig(); restoreLayout(KMKernel::config(), "Header-Geometry"); setShowSortIndicator(true); setFocusPolicy( WheelFocus ); addColumn( i18n("Subject"), 310 ); addColumn( i18n("Sender"), 170 ); addColumn( i18n("Date"), 170 ); if (mPaintInfo.showSize) { addColumn( i18n("Size"), 80 ); setColumnAlignment( mPaintInfo.sizeCol, AlignRight ); showingSize = true; } else { showingSize = false; } if (!pixmapsLoaded) { pixmapsLoaded = TRUE; pixNew = new QPixmap( UserIcon("kmmsgnew") ); pixUns = new QPixmap( UserIcon("kmmsgunseen") ); pixDel = new QPixmap( UserIcon("kmmsgdel") ); pixOld = new QPixmap( UserIcon("kmmsgold") ); pixRep = new QPixmap( UserIcon("kmmsgreplied") ); pixQueued= new QPixmap( UserIcon("kmmsgqueued") ); pixSent = new QPixmap( UserIcon("kmmsgsent") ); pixFwd = new QPixmap( UserIcon("kmmsgforwarded") ); pixFlag = new QPixmap( UserIcon("kmmsgflag") ); pixFullySigned = new QPixmap( UserIcon( "kmmsgfullysigned" ) ); pixPartiallySigned = new QPixmap( UserIcon( "kmmsgpartiallysigned" ) ); pixUndefinedSigned = new QPixmap( UserIcon( "kmmsgundefinedsigned" ) ); pixFullyEncrypted = new QPixmap( UserIcon( "kmmsgfullyencrypted" ) ); pixPartiallyEncrypted = new QPixmap( UserIcon( "kmmsgpartiallyencrypted" ) ); pixUndefinedEncrypted = new QPixmap( UserIcon( "kmmsgundefinedencrypted" ) ); pixFiller = new QPixmap( UserIcon( "kmmsgfiller" ) ); pixEncryptionProblematic = new QPixmap( UserIcon( "kmmsgencryptionproblematic" ) ); pixSignatureProblematic = new QPixmap( UserIcon( "kmmsgsignatureproblematic" ) ); } connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int )), this, SLOT( rightButtonPressed( QListViewItem*, const QPoint &, int ))); connect(this, SIGNAL(doubleClicked(QListViewItem*)), this,SLOT(selectMessage(QListViewItem*))); connect(this,SIGNAL(currentChanged(QListViewItem*)), this,SLOT(highlightMessage(QListViewItem*))); resetCurrentTime(); beginSelection = 0; endSelection = 0; } //----------------------------------------------------------------------------- KMHeaders::~KMHeaders () { if (mFolder) { writeFolderConfig(); writeSortOrder(); mFolder->close(); } writeConfig(); } //----------------------------------------------------------------------------- bool KMHeaders::eventFilter ( QObject *o, QEvent *e ) { if ( e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == RightButton && o->isA("QHeader") ) { mPopup->popup( static_cast(e)->globalPos() ); return true; } return KListView::eventFilter(o, e); } //----------------------------------------------------------------------------- void KMHeaders::slotToggleSizeColumn () { mPaintInfo.showSize = !mPaintInfo.showSize; mPopup->setItemChecked(mSizeColumn, mPaintInfo.showSize); // we need to write it back so that // the configure-dialog knows the correct status KConfig* config = KMKernel::config(); KConfigGroupSaver saver(config, "General"); config->writeEntry("showMessageSize", mPaintInfo.showSize); setFolder(mFolder); } //----------------------------------------------------------------------------- // Support for backing pixmap void KMHeaders::paintEmptyArea( QPainter * p, const QRect & rect ) { if (mPaintInfo.pixmapOn) p->drawTiledPixmap( rect.left(), rect.top(), rect.width(), rect.height(), mPaintInfo.pixmap, rect.left() + contentsX(), rect.top() + contentsY() ); else p->fillRect( rect, colorGroup().base() ); } bool KMHeaders::event(QEvent *e) { bool result = KMHeadersInherited::event(e); if (e->type() == QEvent::ApplicationPaletteChange) { readColorConfig(); } return result; } //----------------------------------------------------------------------------- void KMHeaders::readColorConfig (void) { KConfig* config = KMKernel::config(); // Custom/System colors KConfigGroupSaver saver(config, "Reader"); QColor c1=QColor(kapp->palette().active().text()); QColor c2=QColor("red"); QColor c3=QColor("blue"); QColor c4=QColor(kapp->palette().active().base()); QColor c5=QColor(0,0x7F,0); QColor c6=KGlobalSettings::alternateBackgroundColor(); if (!config->readBoolEntry("defaultColors",TRUE)) { mPaintInfo.colFore = config->readColorEntry("ForegroundColor",&c1); mPaintInfo.colBack = config->readColorEntry("BackgroundColor",&c4); QPalette newPal = kapp->palette(); newPal.setColor( QColorGroup::Base, mPaintInfo.colBack ); newPal.setColor( QColorGroup::Text, mPaintInfo.colFore ); setPalette( newPal ); mPaintInfo.colNew = config->readColorEntry("NewMessage",&c2); mPaintInfo.colUnread = config->readColorEntry("UnreadMessage",&c3); mPaintInfo.colFlag = config->readColorEntry("FlagMessage",&c5); c6 = config->readColorEntry("AltBackgroundColor",&c6); } else { mPaintInfo.colFore = c1; mPaintInfo.colBack = c4; QPalette newPal = kapp->palette(); newPal.setColor( QColorGroup::Base, c4 ); newPal.setColor( QColorGroup::Text, c1 ); setPalette( newPal ); mPaintInfo.colNew = c2; mPaintInfo.colUnread = c3; mPaintInfo.colFlag = c5; } setAlternateBackground(c6); } //----------------------------------------------------------------------------- void KMHeaders::readConfig (void) { KConfig* config = KMKernel::config(); // Backing pixmap support { // area for config group "Pixmaps" KConfigGroupSaver saver(config, "Pixmaps"); QString pixmapFile = config->readEntry("Headers",""); mPaintInfo.pixmapOn = FALSE; if (!pixmapFile.isEmpty()) { mPaintInfo.pixmapOn = TRUE; mPaintInfo.pixmap = QPixmap( pixmapFile ); } } { // area for config group "General" KConfigGroupSaver saver(config, "General"); mPaintInfo.showSize = config->readBoolEntry("showMessageSize"); mPopup->setItemChecked(mSizeColumn, mPaintInfo.showSize); mPaintInfo.showCryptoIcons = config->readBoolEntry( "showCryptoIcons", false ); KMime::DateFormatter::FormatType t = (KMime::DateFormatter::FormatType) config->readNumEntry("dateFormat", KMime::DateFormatter::Fancy ) ; mDate.setCustomFormat( config->readEntry("customDateFormat", QString::null ) ); mDate.setFormat( t ); } readColorConfig(); // Custom/System fonts { // area for config group "General" KConfigGroupSaver saver(config, "Fonts"); if (!(config->readBoolEntry("defaultFonts",TRUE))) { QFont listFont = QFont("helvetica"); setFont(config->readFontEntry("list-font", &listFont)); dateFont = QFont("courier"); dateFont = config->readFontEntry("list-date-font", &dateFont); } else { dateFont = KGlobalSettings::generalFont(); setFont(dateFont); } } // Behavior { KConfigGroupSaver saver(config, "Behaviour"); mLoopOnGotoUnread = config->readBoolEntry( "LoopOnGotoUnread", true ); mJumpToUnread = config->readBoolEntry( "JumpToUnread", false ); } } //----------------------------------------------------------------------------- void KMHeaders::reset(void) { int top = topItemIndex(); int id = currentItemIndex(); noRepaint = TRUE; clear(); noRepaint = FALSE; mItems.resize(0); updateMessageList(); setCurrentMsg(id); setTopItemByIndex(top); } //----------------------------------------------------------------------------- void KMHeaders::refreshNestedState(void) { bool oldState = mNested != mNestedOverride; NestingPolicy oldNestPolicy = nestingPolicy; KConfig* config = KMKernel::config(); KConfigGroupSaver saver(config, "Geometry"); mNested = config->readBoolEntry( "nestedMessages", FALSE ); nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread ); if ((nestingPolicy != oldNestPolicy) || (oldState != (mNested != mNestedOverride))) { setRootIsDecorated( nestingPolicy != AlwaysOpen && mNested != mNestedOverride ); reset(); } } //----------------------------------------------------------------------------- void KMHeaders::readFolderConfig (void) { KConfig* config = KMKernel::config(); assert(mFolder!=0); KConfigGroupSaver saver(config, "Folder-" + mFolder->idString()); mNestedOverride = config->readBoolEntry( "threadMessagesOverride", false ); mSortCol = config->readNumEntry("SortColumn", (int)KMMsgList::sfDate); mSortDescending = (mSortCol < 0); mSortCol = abs(mSortCol) - 1; mTopItem = config->readNumEntry("Top", 0); mCurrentItem = config->readNumEntry("Current", 0); mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", TRUE ); mPaintInfo.status = config->readBoolEntry( "Status", FALSE ); { //area for config group "Geometry" KConfigGroupSaver saver(config, "Geometry"); mNested = config->readBoolEntry( "nestedMessages", FALSE ); nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread ); } setRootIsDecorated( nestingPolicy != AlwaysOpen && mNested != mNestedOverride ); } //----------------------------------------------------------------------------- void KMHeaders::writeFolderConfig (void) { KConfig* config = KMKernel::config(); int mSortColAdj = mSortCol + 1; assert(mFolder!=0); KConfigGroupSaver saver(config, "Folder-" + mFolder->idString()); config->writeEntry("SortColumn", (mSortDescending ? -mSortColAdj : mSortColAdj)); config->writeEntry("Top", topItemIndex()); config->writeEntry("Current", currentItemIndex()); config->writeEntry("OrderOfArrival", mPaintInfo.orderOfArrival); config->writeEntry("Status", mPaintInfo.status); } //----------------------------------------------------------------------------- void KMHeaders::writeConfig (void) { saveLayout(KMKernel::config(), "Header-Geometry"); } //----------------------------------------------------------------------------- void KMHeaders::setFolder (KMFolder *aFolder, bool jumpToFirst) { CREATE_TIMER(set_folder); START_TIMER(set_folder); int id; QString str; mSortInfo.fakeSort = 0; setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol )); if (mFolder && mFolder==aFolder) { int top = topItemIndex(); id = currentItemIndex(); writeFolderConfig(); readFolderConfig(); updateMessageList(); setCurrentMsg(id); setTopItemByIndex(top); } else { if (mFolder) { // WABA: Make sure that no KMReaderWin is still using a msg // from this folder, since it's msg's are about to be deleted. highlightMessage(0, false); disconnect(mFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)), this, SLOT(setFolderInfoStatus())); mFolder->markNewAsUnread(); writeFolderConfig(); disconnect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)), this, SLOT(msgHeaderChanged(KMFolder*,int))); disconnect(mFolder, SIGNAL(msgAdded(int)), this, SLOT(msgAdded(int))); disconnect(mFolder, SIGNAL(msgRemoved(int,QString)), this, SLOT(msgRemoved(int,QString))); disconnect(mFolder, SIGNAL(changed()), this, SLOT(msgChanged())); disconnect(mFolder, SIGNAL(statusMsg(const QString&)), mOwner, SLOT(statusMsg(const QString&))); writeSortOrder(); mFolder->close(); // System folders remain open but we also should write the index from // time to time if (mFolder->dirty()) mFolder->writeIndex(); } mSortInfo.removed = 0; mFolder = aFolder; mSortInfo.dirty = TRUE; mOwner->editAction->setEnabled(mFolder ? (kernel->folderIsDraftOrOutbox(mFolder)): false ); mOwner->replyListAction()->setEnabled(mFolder ? mFolder->isMailingList() : false); if (mFolder) { connect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)), this, SLOT(msgHeaderChanged(KMFolder*,int))); connect(mFolder, SIGNAL(msgAdded(int)), this, SLOT(msgAdded(int))); connect(mFolder, SIGNAL(msgRemoved(int,QString)), this, SLOT(msgRemoved(int,QString))); connect(mFolder, SIGNAL(changed()), this, SLOT(msgChanged())); connect(mFolder, SIGNAL(statusMsg(const QString&)), mOwner, SLOT(statusMsg(const QString&))); connect(aFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)), this, SLOT(setFolderInfoStatus())); // Not very nice, but if we go from nested to non-nested // in the folderConfig below then we need to do this otherwise // updateMessageList would do something unspeakable if (mNested != mNestedOverride) { noRepaint = TRUE; clear(); noRepaint = FALSE; mItems.resize( 0 ); } readFolderConfig(); CREATE_TIMER(kmfolder_open); START_TIMER(kmfolder_open); mFolder->open(); END_TIMER(kmfolder_open); SHOW_TIMER(kmfolder_open); if (mNested != mNestedOverride) { noRepaint = TRUE; clear(); noRepaint = FALSE; mItems.resize( 0 ); } } } CREATE_TIMER(updateMsg); START_TIMER(updateMsg); updateMessageList(!jumpToFirst); // jumpToFirst seem inverted - don END_TIMER(updateMsg); SHOW_TIMER(updateMsg); makeHeaderVisible(); if (mFolder) setFolderInfoStatus(); QString colText = i18n( "Sender" ); if (mFolder && (mFolder->whoField().lower() == "to")) colText = i18n("Receiver"); setColumnText( mPaintInfo.senderCol, colText); colText = i18n( "Date" ); if (mPaintInfo.orderOfArrival) colText = i18n( "Date (Order of Arrival)" ); setColumnText( mPaintInfo.dateCol, colText); colText = i18n( "Subject" ); if (mPaintInfo.status) colText = colText + i18n( " (Status)" ); setColumnText( mPaintInfo.subCol, colText); if (mFolder) { if (mPaintInfo.showSize) { colText = i18n( "Size" ); if (showingSize) { setColumnText( mPaintInfo.sizeCol, colText); } else { // add in the size field addColumn(colText); setColumnAlignment( mPaintInfo.sizeCol, AlignRight ); } showingSize = true; } else { if (showingSize) { // remove the size field removeColumn(mPaintInfo.sizeCol); } showingSize = false; } } END_TIMER(set_folder); SHOW_TIMER(set_folder); } // QListView::setContentsPos doesn't seem to work // until after the list view has been shown at least // once. void KMHeaders::workAroundQListViewLimitation() { setTopItemByIndex(mTopItem); setCurrentItemByIndex(mCurrentItem); } //----------------------------------------------------------------------------- void KMHeaders::msgChanged() { emit maybeDeleting(); if (mFolder->count() == 0) { // Folder cleared clear(); return; } int i = topItemIndex(); int cur = currentItemIndex(); if (!isUpdatesEnabled()) return; QString msgIdMD5; QListViewItem *item = currentItem(); KMHeaderItem *hi = dynamic_cast(item); if (item && hi) { KMMsgBase *mb = mFolder->getMsgBase(hi->msgId()); if (mb) msgIdMD5 = mb->msgIdMD5(); } if (!isUpdatesEnabled()) return; // prevent IMAP messages from scrolling to top disconnect(this,SIGNAL(currentChanged(QListViewItem*)), this,SLOT(highlightMessage(QListViewItem*))); updateMessageList(); setTopItemByIndex( i ); setCurrentMsg(cur); setSelected( currentItem(), TRUE ); connect(this,SIGNAL(currentChanged(QListViewItem*)), this,SLOT(highlightMessage(QListViewItem*))); // if the current message has changed then emit // the selected signal to force an update // Normally the serial number of the message would be // used to do this, but because we don't yet have // guaranteed serial numbers for IMAP messages fall back // to using the MD5 checksum of the msgId. item = currentItem(); hi = dynamic_cast(item); if (item && hi) { KMMsgBase *mb = mFolder->getMsgBase(hi->msgId()); if (mb) { if (msgIdMD5.isEmpty() || (msgIdMD5 != mb->msgIdMD5())) emit selected(mFolder->getMsg(hi->msgId())); } else { emit selected(0); } } else emit selected(0); } //----------------------------------------------------------------------------- void KMHeaders::msgAdded(int id) { KMHeaderItem* hi = 0; if (!isUpdatesEnabled()) return; mItems.resize( mFolder->count() ); KMMsgBase* mb = mFolder->getMsgBase( id ); assert(mb != 0); // otherwise using count() above is wrong if (mNested != mNestedOverride) { QString msgId = mb->msgIdMD5(); if (msgId.isNull()) msgId = ""; QString replyToId = mb->replyToIdMD5(); if(mIdTree.isEmpty()) { QString md5; for(int x = 0; x < mFolder->count() - 1; x++) { if(mItems[x]) { md5 = mFolder->getMsgBase(x)->msgIdMD5(); if(md5.isEmpty()) continue; if(mIdTree[md5]) ; else mIdTree.insert(md5, mItems[x]); } } } if (replyToId.isEmpty() || !mIdTree[replyToId]) hi = new KMHeaderItem( this, id ); else { KMHeaderItem *parent = mIdTree[replyToId]; assert(parent); hi = new KMHeaderItem( parent, id ); } if (!mIdTree[msgId]) mIdTree.replace( msgId, hi ); } else hi = new KMHeaderItem( this, id ); mItems[id] = hi; appendUnsortedItem(hi); //inserted into sorted list if (mSortInfo.fakeSort) { QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); KMHeadersInherited::setSorting(mSortCol, !mSortDescending ); mSortInfo.fakeSort = 0; } msgHeaderChanged(mFolder,id); if ((childCount() == 1) && hi) { setSelected( hi, true ); setCurrentItem( firstChild() ); // ### workaround the fact that Qt 3.0.1's QListView doesn't emit // currentChanged() on setCurrentItem(): /*own slot*/highlightMessage( firstChild() ); } } //----------------------------------------------------------------------------- void KMHeaders::msgRemoved(int id, QString msgId) { if (!isUpdatesEnabled()) return; if ((id < 0) || (id >= (int)mItems.size())) return; mIdTree.remove(msgId); // Reparent children of item into top level QListViewItem *myParent = mItems[id]; QListViewItem *myChild = myParent->firstChild(); QListViewItem *threadRoot = myParent; while (threadRoot->parent()) threadRoot = threadRoot->parent(); QString key = static_cast(threadRoot)->key(mSortCol, !mSortDescending); while (myChild) { QListViewItem *lastChild = myChild; KMHeaderItem *item = static_cast(myChild); item->setTempKey( key + item->key( mSortCol, !mSortDescending )); myChild = myChild->nextSibling(); myParent->takeItem(lastChild); if (mSortInfo.fakeSort) { QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); KMHeadersInherited::setSorting(mSortCol, !mSortDescending ); mSortInfo.fakeSort = 0; } insertItem(lastChild); } if (currentItem() == mItems[id]) mPrevCurrent = 0; KMHeaderItem *removedItem = mItems[id]; for (int i = id; i < (int)mItems.size() - 1; ++i) { mItems[i] = mItems[i+1]; mItems[i]->setMsgId( i ); } mItems.resize( mItems.size() - 1 ); QListViewItem *next = removedItem->itemBelow(); if (next && removedItem == static_cast(currentItem()) ) { setCurrentItem( next ); setSelected( next, TRUE ); } delete removedItem; } //----------------------------------------------------------------------------- void KMHeaders::msgHeaderChanged(KMFolder*, int msgId) { if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return; KMHeaderItem *item = mItems[msgId]; if (item) { item->irefresh(); item->repaint(); } } //----------------------------------------------------------------------------- void KMHeaders::setMsgStatus (KMMsgStatus status) { SerNumList serNums; for (QListViewItemIterator it(this); it.current(); it++) if (it.current()->isSelected()) { KMHeaderItem *item = static_cast(it.current()); KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); serNums.append( msgBase->getMsgSerNum() ); } if (serNums.empty()) return; KMCommand *command = new KMSetStatusCommand( status, serNums ); command->start(); } QPtrList KMHeaders::currentThread() const { if (!mFolder) return QPtrList(); // starting with the current item... QListViewItem *curItem = currentItem(); if (!curItem) return QPtrList(); // ...find the top-level item: QListViewItem *topOfThread = curItem; while ( topOfThread->parent() ) topOfThread = topOfThread->parent(); // collect the items in this thread: QPtrList list; QListViewItem *topOfNextThread = topOfThread->nextSibling(); for ( QListViewItemIterator it( topOfThread ) ; it.current() && it.current() != topOfNextThread ; ++it ) list.append( it.current() ); return list; } void KMHeaders::setThreadStatus(KMMsgStatus status) { QPtrList curThread = currentThread(); QPtrListIterator it( curThread ); SerNumList serNums; for ( it.toFirst() ; it.current() ; ++it ) { int id = static_cast(*it)->msgId(); KMMsgBase *msgBase = mFolder->getMsgBase( id ); serNums.append( msgBase->getMsgSerNum() ); } if (serNums.empty()) return; KMCommand *command = new KMSetStatusCommand( status, serNums ); command->start(); } //----------------------------------------------------------------------------- int KMHeaders::slotFilterMsg(KMMessage *msg) { msg->setTransferInProgress(false); int filterResult = kernel->filterMgr()->process(msg,KMFilterMgr::Explicit); if (filterResult == 2) { // something went horribly wrong (out of space?) kernel->emergencyExit( i18n("Unable to process messages (out of space?)" )); return 2; } if (msg->parent()) { // unGet this msg int idx = -1; KMFolder * p = 0; kernel->msgDict()->getLocation( msg, &p, &idx ); assert( p == msg->parent() ); assert( idx >= 0 ); p->unGetMsg( idx ); } return filterResult; } void KMHeaders::slotExpandOrCollapseThread( bool expand ) { if ( !isThreaded() ) return; // find top-level parent of currentItem(). QListViewItem *item = currentItem(); if ( !item ) return; while ( item->parent() ) item = item->parent(); KMHeaderItem * hdrItem = static_cast(item); hdrItem->setOpenRecursive( expand ); if ( !expand ) // collapse can hide the current item: setCurrentMsg( hdrItem->msgId() ); ensureItemVisible( currentItem() ); } void KMHeaders::slotExpandOrCollapseAllThreads( bool expand ) { if ( !isThreaded() ) return; for ( QListViewItem *item = firstChild() ; item ; item = item->nextSibling() ) static_cast(item)->setOpenRecursive( expand ); if ( !expand ) { // collapse can hide the current item: QListViewItem * item = currentItem(); if( item ) { while ( item->parent() ) item = item->parent(); setCurrentMsg( static_cast(item)->msgId() ); } } ensureItemVisible( currentItem() ); } //----------------------------------------------------------------------------- void KMHeaders::setFolderInfoStatus () { QString str; str = i18n("%n message, %1.", "%n messages, %1.", mFolder->count()) .arg(i18n("%n unread", "%n unread", mFolder->countUnread())); if (mFolder->isReadOnly()) str += i18n("Folder is read-only."); KMBroadcastStatus::instance()->setStatusMsg(str); } //----------------------------------------------------------------------------- void KMHeaders::applyFiltersOnMsg() { KMMsgBase* msgBase; KMMessage* msg; emit maybeDeleting(); disconnect(this,SIGNAL(currentChanged(QListViewItem*)), this,SLOT(highlightMessage(QListViewItem*))); KMMessageList* msgList = selectedMsgs(); int topX = contentsX(); int topY = contentsY(); if (msgList->isEmpty()) return; QListViewItem *qlvi = currentItem(); QListViewItem *next = qlvi; while (next && next->isSelected()) next = next->itemBelow(); if (!next || (next && next->isSelected())) { next = qlvi; while (next && next->isSelected()) next = next->itemAbove(); } clearSelection(); for (msgBase=msgList->first(); msgBase; msgBase=msgList->next()) { int idx = msgBase->parent()->find(msgBase); assert(idx != -1); msg = mFolder->getMsg(idx); if (msg->transferInProgress()) continue; msg->setTransferInProgress(true); if ( !msg->isComplete() ) { FolderJob *job = mFolder->createJob(msg); connect(job, SIGNAL(messageRetrieved(KMMessage*)), SLOT(slotFilterMsg(KMMessage*))); job->start(); } else { if (slotFilterMsg(msg) == 2) break; } } kernel->filterMgr()->cleanup(); setContentsPos( topX, topY ); emit selected( 0 ); if (next) { setCurrentItem( next ); setSelected( next, TRUE ); highlightMessage( next, true); } else if (currentItem()) { setSelected( currentItem(), TRUE ); highlightMessage( currentItem(), true); } else emit selected( 0 ); makeHeaderVisible(); connect(this,SIGNAL(currentChanged(QListViewItem*)), this,SLOT(highlightMessage(QListViewItem*))); } //----------------------------------------------------------------------------- void KMHeaders::setMsgRead (int msgId) { KMMsgBase *msgBase = mFolder->getMsgBase( msgId ); if (!msgBase) return; SerNumList serNums; KMMsgStatus st = msgBase->status(); if (st==KMMsgStatusNew || st==KMMsgStatusUnread || st==KMMsgStatusRead) { serNums.append( msgBase->getMsgSerNum() ); } KMCommand *command = new KMSetStatusCommand( KMMsgStatusOld, serNums ); command->start(); } //----------------------------------------------------------------------------- void KMHeaders::deleteMsg () { //make sure we have an associated folder (root of folder tree does not). if (!mFolder) return; KMMessageList msgList = *selectedMsgs(); KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList, this ); command->start(); KMBroadcastStatus::instance()->setStatusMsg(""); // triggerUpdate(); } //----------------------------------------------------------------------------- void KMHeaders::resendMsg () { KMComposeWin *win; KMMessage *newMsg, *msg = currentMsg(); if (!msg || !msg->codec()) return; kernel->kbp()->busy(); newMsg = new KMMessage; newMsg->fromString(msg->asString()); newMsg->initFromMessage(msg, true); newMsg->setCharset(msg->codec()->mimeName()); newMsg->setTo(msg->to()); newMsg->setSubject(msg->subject()); // the message needs a new Message-Id newMsg->removeHeaderField( "Message-Id" ); win = new KMComposeWin(); win->setMsg(newMsg, FALSE, true); win->show(); kernel->kbp()->idle(); } //----------------------------------------------------------------------------- void KMHeaders::moveSelectedToFolder( int menuId ) { if (mMenuToFolder[menuId]) moveMsgToFolder( mMenuToFolder[menuId] ); } //----------------------------------------------------------------------------- void KMHeaders::prepareMove( KMMsgBase **curMsg, int *contentX, int *contentY ) { emit maybeDeleting(); disconnect( this, SIGNAL(currentChanged(QListViewItem*)), this, SLOT(highlightMessage(QListViewItem*))); QListViewItem *curItem; KMHeaderItem *item; curItem = currentItem(); while (curItem && curItem->isSelected() && curItem->itemBelow()) curItem = curItem->itemBelow(); while (curItem && curItem->isSelected() && curItem->itemAbove()) curItem = curItem->itemAbove(); item = static_cast(curItem); if (item && !item->isSelected()) *curMsg = mFolder->getMsgBase(item->msgId()); *contentX = contentsX(); *contentY = contentsY(); // The following is a rather delicate process. We can't allow getMsg // to be called on messages in msgList as then we will try to operate on a // dangling pointer below (assuming a KMMsgInfo* is deleted and a KMMessage* // is created when getMsg is called). // // But KMMainWidget was modified recently so that exactly that happened // (a slot was connected to the selectionChanged signal. // // So we block all signals for awhile to avoid this. blockSignals( true ); // don't emit signals when the current message is } //----------------------------------------------------------------------------- void KMHeaders::finalizeMove( KMMsgBase *curMsg, int contentX, int contentY ) { blockSignals( false ); emit selected( 0 ); if (curMsg) { setSelected( currentItem(), TRUE ); setCurrentMsg( mFolder->find( curMsg ) ); highlightMessage( currentItem(), false); } setContentsPos( contentX, contentY ); makeHeaderVisible(); connect( this, SIGNAL(currentChanged(QListViewItem*)), this, SLOT(highlightMessage(QListViewItem*))); } //----------------------------------------------------------------------------- void KMHeaders::moveMsgToFolder (KMFolder* destFolder) { KMMessageList msgList = *selectedMsgs(); if ( !destFolder && // messages shall be deleted KMessageBox::warningContinueCancel(this, i18n("Do you really want to delete the selected message?
" "Once deleted, it cannot be restored!
", "Do you really want to delete the %n selected messages?
" "Once deleted, they cannot be restored!
", msgList.count() ), i18n("Delete Messages"), i18n("De&lete"), "NoConfirmDelete") == KMessageBox::Cancel ) return; // user cancelled the action KMCommand *command = new KMMoveCommand( destFolder, msgList, this ); command->start(); } bool KMHeaders::canUndo() const { return ( kernel->undoStack()->size() > 0 ); } //----------------------------------------------------------------------------- void KMHeaders::undo() { KMMessage *msg; ulong serNum; int idx = -1; KMFolder *folder, *curFolder, *oldCurFolder; if (kernel->undoStack()->popAction(serNum, folder, oldCurFolder)) { kernel->msgDict()->getLocation(serNum, &curFolder, &idx); if (idx == -1 || curFolder != oldCurFolder) return; curFolder->open(); msg = curFolder->getMsg( idx ); folder->moveMsg( msg ); if (folder->count() > 1) folder->unGetMsg( folder->count() - 1 ); curFolder->close(); } else { // Sorry.. stack is empty.. KMessageBox::sorry(this, i18n("There is nothing to undo!")); } } //----------------------------------------------------------------------------- void KMHeaders::copySelectedToFolder(int menuId ) { if (mMenuToFolder[menuId]) copyMsgToFolder( mMenuToFolder[menuId] ); } //----------------------------------------------------------------------------- void KMHeaders::copyMsgToFolder(KMFolder* destFolder, KMMessage* aMsg) { KMMessageList msgList; if (aMsg) msgList.append( aMsg ); else msgList = *selectedMsgs(); if (!destFolder) return; KMCommand *command = new KMCopyCommand( destFolder, msgList ); command->start(); } //----------------------------------------------------------------------------- void KMHeaders::setCurrentMsg(int cur) { if (!mFolder) return; if (cur >= mFolder->count()) cur = mFolder->count() - 1; if ((cur >= 0) && (cur < (int)mItems.size())) { clearSelection(); setCurrentItem( mItems[cur] ); setSelected( mItems[cur], TRUE ); } makeHeaderVisible(); setFolderInfoStatus(); } //----------------------------------------------------------------------------- KMMessageList* KMHeaders::selectedMsgs() { mSelMsgBaseList.clear(); for (QListViewItemIterator it(this); it.current(); it++) if (it.current()->isSelected()) { KMHeaderItem *item = static_cast(it.current()); KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); mSelMsgBaseList.append(msgBase); } return &mSelMsgBaseList; } //----------------------------------------------------------------------------- int KMHeaders::firstSelectedMsg() const { int selectedMsg = -1; QListViewItem *item; for (item = firstChild(); item; item = item->itemBelow()) if (item->isSelected()) { selectedMsg = (static_cast(item))->msgId(); break; } return selectedMsg; } //----------------------------------------------------------------------------- void KMHeaders::nextMessage() { QListViewItem *lvi = currentItem(); if (lvi && lvi->itemBelow()) { clearSelection(); setSelected( lvi, FALSE ); selectNextMessage(); } } void KMHeaders::selectNextMessage() { QListViewItem *lvi = currentItem(); if( lvi ) { QListViewItem *below = lvi->itemBelow(); QListViewItem *temp = lvi; if (lvi && below ) { while (temp) { temp->firstChild(); temp = temp->parent(); } lvi->repaint(); /* test to see if we need to unselect messages on back track */ (below->isSelected() ? setSelected(lvi, FALSE) : setSelected(below, TRUE)); setCurrentItem(below); makeHeaderVisible(); setFolderInfoStatus(); } } } //----------------------------------------------------------------------------- void KMHeaders::prevMessage() { QListViewItem *lvi = currentItem(); if (lvi && lvi->itemAbove()) { clearSelection(); setSelected( lvi, FALSE ); selectPrevMessage(); } } void KMHeaders::selectPrevMessage() { QListViewItem *lvi = currentItem(); if( lvi ) { QListViewItem *above = lvi->itemAbove(); QListViewItem *temp = lvi; if (lvi && above) { while (temp) { temp->firstChild(); temp = temp->parent(); } lvi->repaint(); /* test to see if we need to unselect messages on back track */ (above->isSelected() ? setSelected(lvi, FALSE) : setSelected(above, TRUE)); setCurrentItem(above); makeHeaderVisible(); setFolderInfoStatus(); } } } //----------------------------------------------------------------------------- void KMHeaders::findUnreadAux( KMHeaderItem*& item, bool & foundUnreadMessage, bool onlyNew, bool aDirNext ) { KMMsgBase* msgBase = 0; KMHeaderItem *lastUnread = 0; /* itemAbove() is _slow_ */ if (aDirNext) { while (item) { msgBase = mFolder->getMsgBase(item->msgId()); if (msgBase && msgBase->isUnread()) foundUnreadMessage = true; if (!onlyNew && msgBase && msgBase->isUnread()) break; if (onlyNew && msgBase && msgBase->isNew()) break; item = static_cast(item->itemBelow()); } } else { KMHeaderItem *newItem = static_cast(firstChild()); while (newItem) { msgBase = mFolder->getMsgBase(newItem->msgId()); if (msgBase && msgBase->isUnread()) foundUnreadMessage = true; if (!onlyNew && msgBase && msgBase->isUnread() || onlyNew && msgBase && msgBase->isNew()) lastUnread = newItem; if (newItem == item) break; newItem = static_cast(newItem->itemBelow()); } item = lastUnread; } } //----------------------------------------------------------------------------- int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent) { KMHeaderItem *item, *pitem; bool foundUnreadMessage = false; if (!mFolder) return -1; if (!(mFolder->count()) > 0) return -1; if ((aStartAt >= 0) && (aStartAt < (int)mItems.size())) item = mItems[aStartAt]; else { item = currentHeaderItem(); if (!item) { if (aDirNext) item = static_cast(firstChild()); else item = static_cast(lastChild()); } if (!item) return -1; if ( !acceptCurrent ) if (aDirNext) item = static_cast(item->itemBelow()); else item = static_cast(item->itemAbove()); } pitem = item; findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext ); // We have found an unread item, but it is not necessary the // first unread item. // // Find the ancestor of the unread item closest to the // root and recursively sort all of that ancestors children. if (item) { QListViewItem *next = item; while (next->parent()) next = next->parent(); next = static_cast(next)->firstChildNonConst(); while (next && (next != item)) if (static_cast(next)->firstChildNonConst()) next = next->firstChild(); else if (next->nextSibling()) next = next->nextSibling(); else { while (next && (next != item)) { next = next->parent(); if (next == item) break; if (next && next->nextSibling()) { next = next->nextSibling(); break; } } } } item = pitem; findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext ); if (item) return item->msgId(); // A kludge to try to keep the number of unread messages in sync int unread = mFolder->countUnread(); if (((unread == 0) && foundUnreadMessage) || ((unread > 0) && !foundUnreadMessage)) { mFolder->correctUnreadMsgsCount(); } return -1; } //----------------------------------------------------------------------------- void KMHeaders::nextUnreadMessage(bool acceptCurrent) { if ( !mFolder->countUnread() ) return; int i = findUnread(TRUE, -1, false, acceptCurrent); if ( i < 0 && mLoopOnGotoUnread ) { KMHeaderItem * first = static_cast(firstChild()); if ( first ) i = findUnread(TRUE, first->msgId(), false, acceptCurrent); // from top } setCurrentMsg(i); ensureCurrentItemVisible(); } void KMHeaders::ensureCurrentItemVisible() { int i = currentItemIndex(); if ((i >= 0) && (i < (int)mItems.size())) center( contentsX(), itemPos(mItems[i]), 0, 9.0 ); } //----------------------------------------------------------------------------- void KMHeaders::prevUnreadMessage() { if ( !mFolder->countUnread() ) return; int i = findUnread(FALSE); if ( i < 0 && mLoopOnGotoUnread ) { KMHeaderItem * last = static_cast(lastItem()); if ( last ) i = findUnread(FALSE, last->msgId() ); // from bottom } setCurrentMsg(i); ensureCurrentItemVisible(); } //----------------------------------------------------------------------------- void KMHeaders::slotNoDrag() { mousePressed = FALSE; } //----------------------------------------------------------------------------- void KMHeaders::makeHeaderVisible() { if (currentItem()) ensureItemVisible( currentItem() ); } //----------------------------------------------------------------------------- void KMHeaders::highlightMessage(QListViewItem* lvi, bool markitread) { KMHeaderItem *item = static_cast(lvi); if (lvi != mPrevCurrent) { if (mPrevCurrent) { KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId()); if (prevMsg) { mFolder->ignoreJobsForMessage(prevMsg); if (!prevMsg->transferInProgress()) mFolder->unGetMsg(mPrevCurrent->msgId()); } } mPrevCurrent = item; } if (!item) { emit selected( 0 ); return; } int idx = item->msgId(); KMMessage *msg = mFolder->getMsg(idx); if (!msg || msg->transferInProgress()) { emit selected( 0 ); mPrevCurrent = 0; return; } KMBroadcastStatus::instance()->setStatusMsg(""); if (markitread && idx >= 0) setMsgRead(idx); mItems[idx]->irefresh(); mItems[idx]->repaint(); emit selected(mFolder->getMsg(idx)); setFolderInfoStatus(); } void KMHeaders::resetCurrentTime() { mDate.reset(); QTimer::singleShot( 1000, this, SLOT( resetCurrentTime() ) ); } //----------------------------------------------------------------------------- void KMHeaders::selectMessage(QListViewItem* lvi) { KMHeaderItem *item = static_cast(lvi); if (!item) return; int idx = item->msgId(); KMMessage *msg = mFolder->getMsg(idx); if (!msg->transferInProgress()) { emit activated(mFolder->getMsg(idx)); } // if (kernel->folderIsDraftOrOutbox(mFolder)) // setOpen(lvi, !lvi->isOpen()); } //----------------------------------------------------------------------------- void KMHeaders::recursivelyAddChildren( int i, KMHeaderItem *parent ) { KMMsgBase* mb; mb = mFolder->getMsgBase( i ); assert( mb ); QString msgId = mb->msgIdMD5(); if (msgId.isNull()) msgId = ""; mIdTree.replace( msgId, parent ); assert( mTreeSeen[msgId] ); if (*(mTreeSeen[msgId])) // this can happen in the pathological case of // multiple messages having the same id. This case, even the extra // pathological version where messages have the same id and different // reply-To-Ids, should be handled ok. Later messages with duplicate // ids will be shown as children of the first one in the bunch. return; mTreeSeen.replace( msgId, &mTrue ); // iterator over items in children list (exclude parent) // recusively add them as children of parent QValueList *messageList = mTree[msgId]; assert(messageList); QValueList::Iterator it; for (it = messageList->begin(); it != messageList->end(); ++it) { if (*it == i) continue; KMHeaderItem* hi = new KMHeaderItem( parent, *it ); assert(mItems[*it] == 0); mItems.operator[](*it) = hi; recursivelyAddChildren( *it, hi ); } } //----------------------------------------------------------------------------- void KMHeaders::updateMessageList(bool set_selection) { mPrevCurrent = 0; KMHeadersInherited::setSorting( mSortCol, !mSortDescending ); if (!mFolder) { noRepaint = TRUE; clear(); noRepaint = FALSE; mItems.resize(0); repaint(); return; } readSortOrder(set_selection); } //----------------------------------------------------------------------------- // KMail Header list selection/navigation description // // If the selection state changes the reader window is updated to show the // current item. // // (The selection state of a message or messages can be changed by pressing // space, or normal/shift/cntrl clicking). // // The following keyboard events are supported when the messages headers list // has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End, // Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do // not change the selection state. // // See contentsMousePressEvent below for a description of mouse selection // behaviour. // // Exception: When shift selecting either with mouse or key press the reader // window is updated regardless of whether of not the selection has changed. void KMHeaders::keyPressEvent( QKeyEvent * e ) { bool cntrl = (e->state() & ControlButton ); bool shft = (e->state() & ShiftButton ); QListViewItem *cur = currentItem(); if (!e || !firstChild()) return; // If no current item, make some first item current when a key is pressed if (!cur) { setCurrentItem( firstChild() ); return; } // Handle space key press if (cur->isSelectable() && e->ascii() == ' ' ) { setSelected( cur, !cur->isSelected() ); highlightMessage( cur, false); return; } if (cntrl) { if (!shft) disconnect(this,SIGNAL(currentChanged(QListViewItem*)), this,SLOT(highlightMessage(QListViewItem*))); switch (e->key()) { case Key_Down: case Key_Up: case Key_Home: case Key_End: case Key_Next: case Key_Prior: case Key_Escape: KMHeadersInherited::keyPressEvent( e ); } if (!shft) connect(this,SIGNAL(currentChanged(QListViewItem*)), this,SLOT(highlightMessage(QListViewItem*))); } } //----------------------------------------------------------------------------- // Handle RMB press, show pop up menu void KMHeaders::rightButtonPressed( QListViewItem *lvi, const QPoint &, int ) { if (!lvi) return; if (!(lvi->isSelected())) { clearSelection(); } setSelected( lvi, TRUE ); slotRMB(); } //----------------------------------------------------------------------------- // KMail mouse selection - simple description // Normal click - select and make current just this item unselect all others // Shift click - select all items from current item to clicked item // can be used multiple times // Cntrl click - select this item in addition to current selection make this // item the current item. void KMHeaders::contentsMousePressEvent(QMouseEvent* e) { // This slot isn't called anymore if the RMB is pressed (Qt 3.0.1) //kdDebug(5006) << "MB pressed: " << e->button() << endl; beginSelection = currentItem(); presspos = e->pos(); QListViewItem *lvi = itemAt( contentsToViewport( e->pos() )); if (!lvi) { KMHeadersInherited::contentsMousePressEvent(e); return; } setCurrentItem( lvi ); if ((e->button() == LeftButton) && !(e->state() & ControlButton) && !(e->state() & ShiftButton)) { mousePressed = TRUE; if (!(lvi->isSelected())) { clearSelection(); KMHeadersInherited::contentsMousePressEvent(e); } else { KMHeadersInherited::contentsMousePressEvent(e); lvi->setSelected( TRUE ); } } else if ((e->button() == LeftButton) && (e->state() & ShiftButton)) { if (!shiftSelection( beginSelection, lvi )) shiftSelection( lvi, beginSelection ); mousePressed = TRUE; } else if ((e->button() == LeftButton) && (e->state() & ControlButton)) { setSelected( lvi, !lvi->isSelected() ); mousePressed = TRUE; } } //----------------------------------------------------------------------------- void KMHeaders::contentsMouseReleaseEvent(QMouseEvent* e) { QListViewItem *endSelection = itemAt( contentsToViewport( e->pos() )); if ((e->button() == LeftButton) && !(e->state() & ControlButton) && !(e->state() & ShiftButton)) { clearSelectionExcept( endSelection ); } if (e->button() != RightButton) KMHeadersInherited::contentsMouseReleaseEvent(e); beginSelection = 0; endSelection = 0; mousePressed = FALSE; } //----------------------------------------------------------------------------- void KMHeaders::contentsMouseMoveEvent( QMouseEvent* e ) { if (mousePressed && (e->pos() - presspos).manhattanLength() > KGlobalSettings::dndEventDelay()) { mousePressed = FALSE; QListViewItem *item = itemAt( contentsToViewport(presspos) ); if ( item ) { QStoredDrag *d = new QStoredDrag("x-kmail-drag/message", viewport()); // Set the drag data to be list of serial numbers selected QByteArray serNumArray; QBuffer serNumBuffer( serNumArray ); serNumBuffer.open( IO_WriteOnly ); QDataStream serNumStream( &serNumBuffer ); unsigned int count = 0; for( QListViewItemIterator it(this); it.current(); it++ ) if( it.current()->isSelected() ) { KMHeaderItem *item = static_cast(it.current()); KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId()); KMFolder *pFolder = msgBase->parent(); serNumStream << kernel->msgDict()->getMsgSerNum(pFolder, pFolder->find( msgBase ) ); count++; } serNumBuffer.close(); d->setEncodedData( serNumArray ); // Set pixmap QPixmap pixmap; if( count == 1 ) pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) ); else pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) ); // Calculate hotspot (as in Konqueror) if( !pixmap.isNull() ) { QPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 ); d->setPixmap( pixmap, hotspot ); } d->drag(); } } } void KMHeaders::highlightMessage(QListViewItem* i) { highlightMessage( i, false ); } //----------------------------------------------------------------------------- void KMHeaders::clearSelectionExcept( QListViewItem *exception ) { QListViewItem *item; for (item = firstChild(); item; item = item->itemBelow()) if (item->isSelected() && (item != exception)) setSelected( item, FALSE ); } //----------------------------------------------------------------------------- bool KMHeaders::shiftSelection( QListViewItem *begin, QListViewItem *end ) { QListViewItem *search = begin; while (search && search->itemBelow() && (search != end)) search = search->itemBelow(); if (search && (search == end)) { while (search && (search != begin)) { setSelected( search, TRUE ); search = search->itemAbove(); } setSelected( search, TRUE ); return TRUE; } return FALSE; } //----------------------------------------------------------------------------- void KMHeaders::slotRMB() { if (!topLevelWidget()) return; // safe bet if (currentMsg()->transferInProgress()) return; QPopupMenu *menu = new QPopupMenu(this); mMenuToFolder.clear(); mOwner->updateMessageMenu(); QPopupMenu *msgMoveMenu = new QPopupMenu(menu); KMMoveCommand::folderToPopupMenu( TRUE, this, &mMenuToFolder, msgMoveMenu ); QPopupMenu *msgCopyMenu = new QPopupMenu(menu); KMCopyCommand::folderToPopupMenu( FALSE, this, &mMenuToFolder, msgCopyMenu ); bool out_folder = kernel->folderIsDraftOrOutbox(mFolder); if ( out_folder ) mOwner->editAction->plug(menu); else { // show most used actions mOwner->replyAction()->plug(menu); mOwner->replyAllAction()->plug(menu); mOwner->replyListAction()->plug(menu); mOwner->forwardMenu()->plug(menu); mOwner->bounceAction()->plug(menu); mOwner->sendAgainAction->plug(menu); } menu->insertSeparator(); menu->insertItem(i18n("&Copy To"), msgCopyMenu); menu->insertItem(i18n("&Move To"), msgMoveMenu); if ( !out_folder ) { mOwner->statusMenu->plug( menu ); // Mark Message menu if ( mOwner->threadStatusMenu->isEnabled() ) mOwner->threadStatusMenu->plug( menu ); // Mark Thread menu } menu->insertSeparator(); mOwner->trashAction->plug(menu); mOwner->deleteAction->plug(menu); menu->insertSeparator(); mOwner->saveAsAction->plug(menu); mOwner->printAction()->plug(menu); if ( !out_folder ) { menu->insertSeparator(); mOwner->action("apply_filters")->plug(menu); mOwner->filterMenu()->plug( menu ); // Create Filter menu } mOwner->action("apply_filter_actions")->plug(menu); menu->exec (QCursor::pos(), 0); delete menu; } //----------------------------------------------------------------------------- KMMessage* KMHeaders::currentMsg() { KMHeaderItem *hi = currentHeaderItem(); if (!hi) return 0; else return mFolder->getMsg(hi->msgId()); } //----------------------------------------------------------------------------- KMHeaderItem* KMHeaders::currentHeaderItem() { return static_cast(currentItem()); } //----------------------------------------------------------------------------- int KMHeaders::currentItemIndex() { KMHeaderItem* item = currentHeaderItem(); if (item) return item->msgId(); else return -1; } //----------------------------------------------------------------------------- void KMHeaders::setCurrentItemByIndex(int msgIdx) { if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) { clearSelection(); bool unchanged = (currentItem() == mItems[msgIdx]); setCurrentItem( mItems[msgIdx] ); setSelected( mItems[msgIdx], TRUE ); if (unchanged) highlightMessage( mItems[msgIdx], false); } } //----------------------------------------------------------------------------- int KMHeaders::topItemIndex() { KMHeaderItem *item = static_cast(itemAt(QPoint(1,1))); if (item) return item->msgId(); else return -1; } // If sorting ascending by date/ooa then try to scroll list when new mail // arrives to show it, but don't scroll current item out of view. void KMHeaders::showNewMail() { if (mSortCol != mPaintInfo.dateCol) return; for( int i = 0; i < (int)mItems.size(); ++i) if (mFolder->getMsgBase(i)->isNew()) { if (!mSortDescending) setTopItemByIndex( currentItemIndex() ); break; } } //----------------------------------------------------------------------------- void KMHeaders::setTopItemByIndex( int aMsgIdx) { int msgIdx = aMsgIdx; if (msgIdx < 0) msgIdx = 0; else if (msgIdx >= (int)mItems.size()) msgIdx = mItems.size() - 1; if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) setContentsPos( 0, itemPos( mItems[msgIdx] )); } //----------------------------------------------------------------------------- void KMHeaders::setNestedOverride( bool override ) { mSortInfo.dirty = TRUE; mNestedOverride = override; setRootIsDecorated( nestingPolicy != AlwaysOpen && mNested != mNestedOverride ); QString sortFile = mFolder->indexLocation() + ".sorted"; unlink(sortFile.local8Bit()); reset(); } //----------------------------------------------------------------------------- void KMHeaders::setOpen( QListViewItem *item, bool open ) { if ((nestingPolicy != AlwaysOpen)|| open) ((KMHeaderItem*)item)->setOpenRecursive( open ); } //----------------------------------------------------------------------------- void KMHeaders::setSorting( int column, bool ascending ) { if (column != -1) { if (column != mSortCol) setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol )); if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); mSortInfo.dirty = TRUE; } mSortCol = column; mSortDescending = !ascending; if (!ascending && (column == mPaintInfo.dateCol)) mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival; if (!ascending && (column == mPaintInfo.subCol)) mPaintInfo.status = !mPaintInfo.status; QString colText = i18n( "Date" ); if (mPaintInfo.orderOfArrival) colText = i18n( "Date (Order of Arrival)" ); setColumnText( mPaintInfo.dateCol, colText); colText = i18n( "Subject" ); if (mPaintInfo.status) colText = colText + i18n( " (Status)" ); setColumnText( mPaintInfo.subCol, colText); } KMHeadersInherited::setSorting( column, ascending ); ensureCurrentItemVisible(); } //Flatten the list and write it to disk -#define KMAIL_SORT_VERSION 1011 +#define KMAIL_SORT_VERSION 1012 #define KMAIL_SORT_FILE(x) x->indexLocation() + ".sorted" #define KMAIL_SORT_HEADER "## KMail Sort V%04d\n\t" #define KMAIL_MAGIC_HEADER_OFFSET 21 //strlen(KMAIL_SORT_HEADER) #define KMAIL_MAX_KEY_LEN 16384 #define KMAIL_RESERVED 3 static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid, int parent_id, QString key, bool update_discover=TRUE) { unsigned long msgSerNum; unsigned long parentSerNum; msgSerNum = kernel->msgDict()->getMsgSerNum( folder, msgid ); if (parent_id >= 0) parentSerNum = kernel->msgDict()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED; else parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED); fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream); fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream); Q_INT32 len = key.length() * sizeof(QChar); fwrite(&len, sizeof(len), 1, sortStream); if (len) fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream); if (update_discover) { //update the discovered change count Q_INT32 discovered_count = 0; fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); fread(&discovered_count, sizeof(discovered_count), 1, sortStream); discovered_count++; fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream); } } bool KMHeaders::writeSortOrder() { QString sortFile = KMAIL_SORT_FILE(mFolder); if (!mSortInfo.dirty) { struct stat stat_tmp; if(stat(sortFile.local8Bit(), &stat_tmp) == -1) { mSortInfo.dirty = TRUE; } } if (mSortInfo.dirty) { QString tempName = sortFile + ".temp"; unlink(tempName.local8Bit()); FILE *sortStream = fopen(tempName.local8Bit(), "w"); if (!sortStream) return FALSE; mSortInfo.dirty = FALSE; fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION); //magic header information Q_INT32 byteOrder = 0x12345678; Q_INT32 column = mSortCol; Q_INT32 ascending= !mSortDescending; Q_INT32 threaded = (mNested != mNestedOverride); Q_INT32 appended=0; Q_INT32 discovered_count = 0; Q_INT32 sorted_count=0; fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream); fwrite(&column, sizeof(column), 1, sortStream); fwrite(&ascending, sizeof(ascending), 1, sortStream); fwrite(&threaded, sizeof(threaded), 1, sortStream); fwrite(&appended, sizeof(appended), 1, sortStream); fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream); fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream); QPtrStack items; { QPtrStack s; for (QListViewItem * i = firstChild(); i; ) { items.push((KMHeaderItem *)i); if ( i->firstChild() ) { s.push( i ); i = i->firstChild(); } else if( i->nextSibling()) { i = i->nextSibling(); } else { for(i=0; !i && s.count(); i = s.pop()->nextSibling()); } } } + + if(mIdTree.isEmpty()) { + for(int x = 0; x < mFolder->count(); x++) { + QString md5; + if(mItems[x]) { + KMMsgBase *mb = mFolder->getMsgBase(x); + md5 = mb->msgIdMD5(); + if (!md5.isEmpty() && !mIdTree[md5]) + mIdTree.insert(md5, mItems[x]); + } + } + } + QDict msgSubjects(mFolder->count()*2); + for(int x = 0; x < mFolder->count(); x++) { + if(mItems[x]) { + QString subjMD5; + KMMsgBase *mb = mFolder->getMsgBase(x); + subjMD5 = mb->strippedSubjectMD5(); + if (subjMD5.isEmpty()) { + mb->initStrippedSubjectMD5(); + subjMD5 = mb->strippedSubjectMD5(); + } + if( !subjMD5.isEmpty() && !msgSubjects.find(subjMD5) ) { + QString replyToIdMD5 = mb->replyToIdMD5(); + QString replyToAuxIdMD5 = mb->replyToAuxIdMD5(); + if ( (replyToIdMD5.isEmpty() || !mIdTree[replyToIdMD5]) + && (replyToAuxIdMD5.isEmpty() || !mIdTree[replyToAuxIdMD5]) ) + msgSubjects.insert(subjMD5, mItems[x]); + } + } + } + KMMsgBase *kmb; while(KMHeaderItem *i = items.pop()) { kmb = mFolder->getMsgBase( i->mMsgId ); QString replymd5 = kmb->replyToIdMD5(); int parent_id = -2; //no parent, top level - if(!replymd5.isEmpty()) { - if(mIdTree.isEmpty()) { - QString md5; - for(int x = 0; x < mFolder->count(); x++) { - if(mItems[x]) { - md5 = mFolder->getMsgBase(x)->msgIdMD5(); - if(md5.isEmpty()) continue; - if(mIdTree[md5]) - ; - else - mIdTree.insert(md5, mItems[x]); - } - } - } - KMHeaderItem *p = mIdTree[replymd5]; - if(p) + KMHeaderItem *p = NULL; + if(!replymd5.isEmpty()) + p = mIdTree[replymd5]; + + if (!p) { + // If we dont have a replyToId, or if we have one and the + // corresponding message is not in this folder, as happens + // if you keep your outgoing messages in an OUTBOX, for + // example, try the list of references, because the second + // to last will likely be in this folder. replyToAuxIdMD5 ontains + // the second to last one. + QString ref = kmb->replyToAuxIdMD5(); + if (!ref.isEmpty()) + p = mIdTree[ref]; + } + // still no parent, let's try by subject + // Force unprefixed subjects that would be threaded by subject + // to the top level. + if (!p && kmb->subjectIsPrefixed()) { + QString subjMD5 = kmb->strippedSubjectMD5(); + if (!subjMD5.isEmpty()) + p = msgSubjects[subjMD5]; + } + if (p) { parent_id = p->mMsgId; - else + if (parent_id == i->mMsgId) + parent_id = -1; + } else { parent_id = -1; } + internalWriteItem(sortStream, mFolder, i->mMsgId, parent_id, i->key(mSortCol, !mSortDescending), FALSE); //double check for magic headers sorted_count++; } //magic header twice, case they've changed fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET); fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream); fwrite(&column, sizeof(column), 1, sortStream); fwrite(&ascending, sizeof(ascending), 1, sortStream); fwrite(&threaded, sizeof(threaded), 1, sortStream); fwrite(&appended, sizeof(appended), 1, sortStream); fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream); fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream); if (sortStream && ferror(sortStream)) { fclose(sortStream); unlink(sortFile.local8Bit()); kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; kernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); } fclose(sortStream); ::rename(tempName.local8Bit(), sortFile.local8Bit()); } return TRUE; } - void KMHeaders::appendUnsortedItem(KMHeaderItem *khi) { QString sortFile = KMAIL_SORT_FILE(mFolder); if(FILE *sortStream = fopen(sortFile.local8Bit(), "r+")) { KMMsgBase *kmb = mFolder->getMsgBase( khi->mMsgId ); int parent_id = -2; //no parent, top level if(khi->parent()) parent_id = ((KMHeaderItem *)khi->parent())->mMsgId; else if(!kmb->replyToIdMD5().isEmpty()) parent_id = -1; internalWriteItem(sortStream, mFolder, khi->mMsgId, parent_id, khi->key(mSortCol, !mSortDescending)); //update the appended flag Q_INT32 appended = 1; fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); fwrite(&appended, sizeof(appended), 1, sortStream); if (sortStream && ferror(sortStream)) { fclose(sortStream); unlink(sortFile.local8Bit()); kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; kernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); } fclose(sortStream); } else { mSortInfo.dirty = TRUE; } } void KMHeaders::dirtySortOrder(int column) { mSortInfo.dirty = TRUE; QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : TRUE); } class KMSortCacheItem { KMHeaderItem *mItem; KMSortCacheItem *mParent; int mId, mSortOffset; QString mKey; QPtrList mSortedChildren; int mUnsortedCount, mUnsortedSize; KMSortCacheItem **mUnsortedChildren; public: KMSortCacheItem() : mItem(0), mParent(0), mId(-1), mSortOffset(-1), mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0) { } KMSortCacheItem(int i, QString k, int o=-1) : mItem(0), mParent(0), mId(i), mSortOffset(o), mKey(k), mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0) { } ~KMSortCacheItem() { if(mUnsortedChildren) free(mUnsortedChildren); } KMSortCacheItem *parent() const { return mParent; } //can't be set, only by the parent bool hasChildren() const { return mSortedChildren.count() || mUnsortedCount; } const QPtrList *sortedChildren() const { return &mSortedChildren; } KMSortCacheItem **unsortedChildren(int &count) const { count = mUnsortedCount; return mUnsortedChildren; } void addSortedChild(KMSortCacheItem *i) { i->mParent = this; mSortedChildren.append(i); } void addUnsortedChild(KMSortCacheItem *i) { i->mParent = this; if(!mUnsortedChildren) mUnsortedChildren = (KMSortCacheItem **)malloc((mUnsortedSize = 25) * sizeof(KMSortCacheItem *)); else if(mUnsortedCount >= mUnsortedSize) mUnsortedChildren = (KMSortCacheItem **)realloc(mUnsortedChildren, (mUnsortedSize *= 2) * sizeof(KMSortCacheItem *)); mUnsortedChildren[mUnsortedCount++] = i; } KMHeaderItem *item() const { return mItem; } void setItem(KMHeaderItem *i) { Q_ASSERT(!mItem); mItem = i; } const QString &key() const { return mKey; } void setKey(const QString &key) { mKey = key; } int id() const { return mId; } void setId(int id) { mId = id; } int offset() const { return mSortOffset; } void setOffset(int x) { mSortOffset = x; } void updateSortFile(FILE *, KMFolder *folder, bool =FALSE); }; void KMSortCacheItem::updateSortFile(FILE *sortStream, KMFolder *folder, bool waiting_for_parent) { if(mSortOffset == -1) { fseek(sortStream, 0, SEEK_END); mSortOffset = ftell(sortStream); } else { fseek(sortStream, mSortOffset, SEEK_SET); } int parent_id = -2; if(!waiting_for_parent) { if(mParent) parent_id = mParent->id(); else parent_id = -1; } internalWriteItem(sortStream, folder, mId, parent_id, mKey); } static bool compare_ascending = FALSE; static int compare_KMSortCacheItem(const void *s1, const void *s2) { if ( !s1 || !s2 ) return 0; KMSortCacheItem **b1 = (KMSortCacheItem **)s1; KMSortCacheItem **b2 = (KMSortCacheItem **)s2; int ret = (*b1)->key().compare((*b2)->key()); if(compare_ascending) ret = -ret; return ret; } + + bool KMHeaders::readSortOrder(bool set_selection) { //all cases Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended; Q_INT32 deleted_count = 0; bool unread_exists = false; QMemArray sortCache(mFolder->count()); KMSortCacheItem root; QString replyToIdMD5; root.setId(-666); //mark of the root! bool error = false; //threaded cases QPtrList unparented; mIdTree.clear(); if (mIdTree.size() < 2*(unsigned)mFolder->count()) mIdTree.resize( 2*mFolder->count() ); //cleanup noRepaint = TRUE; clear(); noRepaint = FALSE; mItems.resize( mFolder->count() ); for (int i=0; icount(); i++) { sortCache[i] = 0; mItems[i] = 0; } QString sortFile = KMAIL_SORT_FILE(mFolder); FILE *sortStream = fopen(sortFile.local8Bit(), "r+"); mSortInfo.fakeSort = 0; if(sortStream) { mSortInfo.fakeSort = 1; int version = 0; if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1) version = -1; if(version == KMAIL_SORT_VERSION) { Q_INT32 byteOrder = 0; fread(&byteOrder, sizeof(byteOrder), 1, sortStream); if (byteOrder == 0x12345678) { fread(&column, sizeof(column), 1, sortStream); fread(&ascending, sizeof(ascending), 1, sortStream); fread(&threaded, sizeof(threaded), 1, sortStream); fread(&appended, sizeof(appended), 1, sortStream); fread(&discovered_count, sizeof(discovered_count), 1, sortStream); fread(&sorted_count, sizeof(sorted_count), 1, sortStream); //Hackyness to work around qlistview problems KMHeadersInherited::setSorting(-1); header()->setSortIndicator(column, ascending); QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int))); //setup mSortInfo here now, as above may change it mSortInfo.dirty = FALSE; mSortInfo.column = (short)column; mSortInfo.ascending = (compare_ascending = ascending); KMSortCacheItem *item; unsigned long serNum, parentSerNum; int id, len, parent, x; QChar *tmp_qchar = 0; int tmp_qchar_len = 0; const int mFolderCount = mFolder->count(); QString key; CREATE_TIMER(parse); START_TIMER(parse); for(x = 0; !feof(sortStream) && !error; x++) { off_t offset = ftell(sortStream); KMFolder *folder; //parse if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) || !fread(&len, sizeof(len), 1, sortStream)) { break; } if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) { kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl; error = true; continue; } if(len) { if(len > tmp_qchar_len) { tmp_qchar = (QChar *)realloc(tmp_qchar, len); tmp_qchar_len = len; } if(!fread(tmp_qchar, len, 1, sortStream)) break; key = QString(tmp_qchar, len / 2); } else { key = QString(""); //yuck } kernel->msgDict()->getLocation(serNum, &folder, &id); if (folder != mFolder) { ++deleted_count; continue; } if (parentSerNum < KMAIL_RESERVED) { parent = (int)parentSerNum - KMAIL_RESERVED; } else { kernel->msgDict()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent); if (folder != mFolder) parent = -1; } if ((id < 0) || (id >= mFolderCount) || (parent < -2) || (parent >= mFolderCount)) { // sanity checking kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl; error = true; continue; } if ((item=sortCache[id])) { if (item->id() != -1) { kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl; error = true; continue; } item->setKey(key); item->setId(id); item->setOffset(offset); } else { item = sortCache[id] = new KMSortCacheItem(id, key, offset); } if (threaded && parent != -2) { if(parent == -1) { unparented.append(item); root.addUnsortedChild(item); } else { if( ! sortCache[parent] ) sortCache[parent] = new KMSortCacheItem; sortCache[parent]->addUnsortedChild(item); } } else { if(x < sorted_count ) root.addSortedChild(item); else { root.addUnsortedChild(item); } } } if (error || (x != sorted_count + discovered_count)) {// sanity check kdDebug(5006) << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl; fclose(sortStream); sortStream = 0; } if(tmp_qchar) free(tmp_qchar); END_TIMER(parse); SHOW_TIMER(parse); } else { fclose(sortStream); sortStream = 0; } } else { fclose(sortStream); sortStream = 0; } } if (!sortStream) { mSortInfo.dirty = TRUE; mSortInfo.column = column = mSortCol; mSortInfo.ascending = ascending = !mSortDescending; threaded = (mNested != mNestedOverride); sorted_count = discovered_count = appended = 0; KMHeadersInherited::setSorting( mSortCol, !mSortDescending ); } //fill in empty holes if((sorted_count + discovered_count - deleted_count) < mFolder->count()) { CREATE_TIMER(holes); START_TIMER(holes); KMMsgBase *msg = 0; for(int x = 0; x < mFolder->count(); x++) { if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) { int sortOrder = column; if (mPaintInfo.orderOfArrival) sortOrder |= (1 << 6); if (mPaintInfo.status) sortOrder |= (1 << 5); sortCache[x] = new KMSortCacheItem( x, KMHeaderItem::generate_key(x, this, msg, &mPaintInfo, sortOrder)); if(threaded) unparented.append(sortCache[x]); else root.addUnsortedChild(sortCache[x]); if(sortStream) sortCache[x]->updateSortFile(sortStream, mFolder, TRUE); discovered_count++; appended = 1; } } END_TIMER(holes); SHOW_TIMER(holes); } //make sure we've placed everything in parent/child relationship if (appended && threaded && !unparented.isEmpty()) { CREATE_TIMER(reparent); START_TIMER(reparent); - KMSortCacheItem *i; + // Build two dictionaries, one with all messages and their ids, and + // one with the md5 hashes of the subject stripped of prefixes such as + // Re: or similar. QDict msgs(mFolder->count() * 2); + QDict msgSubjects(mFolder->count() * 2); for(int x = 0; x < mFolder->count(); x++) { - QString md5 = mFolder->getMsgBase(x)->msgIdMD5(); - if(md5.isEmpty()) continue; + KMMsgBase *mi = mFolder->getMsgBase(x); + QString md5 = mi->msgIdMD5(); + if(!md5.isEmpty()) msgs.insert(md5, sortCache[x]); } + for(int x = 0; x < mFolder->count(); x++) { + KMMsgBase *mi = mFolder->getMsgBase(x); + QString subjMD5 = mi->strippedSubjectMD5(); + if (subjMD5.isEmpty()) { + mFolder->getMsgBase(x)->initStrippedSubjectMD5(); + subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5(); + } + // The first message with a certain subject is where we want to + // thread the other messages with the same suject below. Only keep + // that in the dict. Also only accept messages which would not + // otherwise be threaded by IDs as top level messages to avoid + // circular threading. + if( !subjMD5.isEmpty() && !msgSubjects.find(subjMD5) ) { + QString replyToIdMD5 = mi->replyToIdMD5(); + QString replyToAuxIdMD5 = mi->replyToAuxIdMD5(); + if ( (replyToIdMD5.isEmpty() || !msgs.find(replyToIdMD5)) + && (replyToAuxIdMD5.isEmpty() || !msgs.find(replyToAuxIdMD5)) ) + msgSubjects.insert(subjMD5, sortCache[x]); + } + } for(QPtrListIterator it(unparented); it.current(); ++it) { - replyToIdMD5 = mFolder->getMsgBase((*it)->id())->replyToIdMD5(); - if(!replyToIdMD5.isEmpty() && (i = msgs[replyToIdMD5])) { - i->addUnsortedChild((*it)); + KMSortCacheItem *parent=NULL; + KMMsgBase *msg = mFolder->getMsgBase((*it)->id()); + QString replyToIdMD5 = msg->replyToIdMD5(); + if(!replyToIdMD5.isEmpty()) + parent = msgs[replyToIdMD5]; + if (!parent) { + // If we dont have a replyToId, or if we have one and the + // corresponding message is not in this folder, as happens + // if you keep your outgoing messages in an OUTBOX, for + // example, try the list of references, because the second + // to last will likely be in this folder. replyToAuxIdMD5 + // contains the second to last one. + QString ref = msg->replyToAuxIdMD5(); + if (!ref.isEmpty()) + parent = msgs[ref]; + } + if (!parent && msg->subjectIsPrefixed()) { + // Still no parent, let's try by subject, but only if the + // subject is prefixed. This is necessary to make for + // example cvs commit mailing lists work as expected without + // having to turn threading off alltogether. + // If we have a parent, make sure it's not ourselves + QString subjMD5 = msg->strippedSubjectMD5(); + if (!subjMD5.isEmpty()) { + parent = msgSubjects[subjMD5]; + } + } + // If we have a parent, make sure it's not ourselves. + if ( parent && (parent != (*it)) ) { + parent->addUnsortedChild((*it)); if(sortStream) (*it)->updateSortFile(sortStream, mFolder); - } else { //oh well we tried, to the root with you! + } else { + //oh well we tried, to the root with you! root.addUnsortedChild((*it)); } } END_TIMER(reparent); SHOW_TIMER(reparent); } //create headeritems int first_unread = -1; CREATE_TIMER(header_creation); START_TIMER(header_creation); KMHeaderItem *khi; KMSortCacheItem *i, *new_kci; QPtrQueue s; s.enqueue(&root); do { i = s.dequeue(); const QPtrList *sorted = i->sortedChildren(); int unsorted_count, unsorted_off=0; KMSortCacheItem **unsorted = i->unsortedChildren(unsorted_count); if(unsorted) qsort(unsorted, unsorted_count, sizeof(KMSortCacheItem *), //sort compare_KMSortCacheItem); //merge two sorted lists of siblings for(QPtrListIterator it(*sorted); (unsorted && unsorted_off < unsorted_count) || it.current(); ) { if(it.current() && (!unsorted || unsorted_off >= unsorted_count || (ascending && (*it)->key() >= unsorted[unsorted_off]->key()) || (!ascending && (*it)->key() < unsorted[unsorted_off]->key()))) { new_kci = (*it); ++it; } else { new_kci = unsorted[unsorted_off++]; } if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent continue; if(threaded && i->item()) khi = new KMHeaderItem(i->item(), new_kci->id(), new_kci->key()); else khi = new KMHeaderItem(this, new_kci->id(), new_kci->key()); new_kci->setItem(mItems[new_kci->id()] = khi); if(new_kci->hasChildren()) s.enqueue(new_kci); if(set_selection && mFolder->getMsgBase(new_kci->id())->status() == KMMsgStatusNew || set_selection && mFolder->getMsgBase(new_kci->id())->status() == KMMsgStatusUnread) unread_exists = true; } } while(!s.isEmpty()); for(int x = 0; x < mFolder->count(); x++) { //cleanup if (!sortCache[x]->item()) { // we missed a message, how did that happen ? khi = new KMHeaderItem(this, sortCache[x]->id(), sortCache[x]->key()); sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi); } delete sortCache[x]; sortCache[x] = 0; } if (getNestingPolicy()<2) for (KMHeaderItem *khi=static_cast(firstChild()); khi!=0;khi=static_cast(khi->nextSibling())) khi->setOpen(true); - END_TIMER(header_creation); SHOW_TIMER(header_creation); if(sortStream) { //update the .sorted file now // heuristic for when it's time to rewrite the .sorted file if( discovered_count * discovered_count > sorted_count - deleted_count ) { mSortInfo.dirty = TRUE; } else { //update the appended flag appended = 0; fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET); fwrite(&appended, sizeof(appended), 1, sortStream); } } //show a message CREATE_TIMER(selection); START_TIMER(selection); if(set_selection) { if (unread_exists) { KMHeaderItem *item = static_cast(firstChild()); while (item) { bool isUnread = false; if (mJumpToUnread) // search unread messages if (mFolder->getMsgBase(item->msgId())->status() == KMMsgStatusUnread) isUnread = true; if (mFolder->getMsgBase(item->msgId())->status() == KMMsgStatusNew || isUnread) { first_unread = item->msgId(); break; } item = static_cast(item->itemBelow()); } } if(first_unread == -1 ) { setTopItemByIndex(mTopItem); setCurrentItemByIndex((mCurrentItem >= 0) ? mCurrentItem : 0); } else { setCurrentItemByIndex(first_unread); makeHeaderVisible(); center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 ); } } else { // only reset the selection if we have no current item if (mCurrentItem <= 0) { setTopItemByIndex(mTopItem); setCurrentItemByIndex((mCurrentItem >= 0) ? mCurrentItem : 0); } } END_TIMER(selection); SHOW_TIMER(selection); if (error || (sortStream && ferror(sortStream))) { if ( sortStream ) fclose(sortStream); unlink(sortFile.local8Bit()); kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl; kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl; kernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile )); } if(sortStream) fclose(sortStream); return TRUE; } + //----------------------------------------------------------------------------- #include "kmheaders.moc" diff --git a/kmail/kmkernel.cpp b/kmail/kmkernel.cpp index 532fc04682..4e66775d52 100644 --- a/kmail/kmkernel.cpp +++ b/kmail/kmkernel.cpp @@ -1,1404 +1,1405 @@ #include #include #include #include #include #include #include #include #include "kmkernel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kmmsgindex.h" #include "kmreaderwin.h" #include "kmmainwin.h" #include "kmcomposewin.h" #include "kmfoldermgr.h" #include "kmfolderimap.h" #include "kmfoldercachedimap.h" #include "kmacctcachedimap.h" #include "kmfiltermgr.h" #include "kmfilteraction.h" #include "kmsender.h" #include "kmundostack.h" #include "kmacctmgr.h" #include "kbusyptr.h" #include "kmaddrbook.h" #include "kfileio.h" #include "kmpgpwrap.h" #include "kmversion.h" #include "recentaddresses.h" using KMail::RecentAddresses; #include "kmmsgdict.h" #include "kmmessage.h" #include "kmidentity.h" #include "identitymanager.h" #include "configuredialog.h" #include "kmsystemtray.h" #include #include #include "kmmainwidget.h" #include "kmgroupware.h" #include "kmailicalifaceimpl.h" #include #undef Unsorted #undef None #include #include #include KMKernel *KMKernel::mySelf = 0; /********************************************************************/ /* Constructor and destructor */ /********************************************************************/ KMKernel::KMKernel (QObject *parent, const char *name) : QObject(parent, name), DCOPObject("KMailIface"), mIdentityManager(0), mProgress(0), mConfigureDialog(0) { //kdDebug(5006) << "KMKernel::KMKernel" << endl; mySelf = this; the_startingUp = true; closed_by_user = true; the_firstInstance = true; the_msgDict = 0; the_msgIndex = 0; the_inboxFolder = 0; the_outboxFolder = 0; the_sentFolder = 0; the_trashFolder = 0; the_draftsFolder = 0; the_kbp = 0; the_folderMgr = 0; the_imapFolderMgr = 0; the_searchFolderMgr = 0; the_undoStack = 0; the_acctMgr = 0; the_filterMgr = 0; the_popFilterMgr = 0; the_filterActionDict = 0; the_msgSender = 0; mWin = 0; mGroupware = new KMGroupware( this ); // Set up DCOP interface (void)new KMailICalIfaceImpl( mGroupware ); mSystemTray = 0; mXmlGuiInstance = 0; mDeadLetterTimer = 0; mDeadLetterInterval = 1000*120; // 2 minutes new KMpgpWrap(); // register our own (libkdenetwork) utf-7 codec as long as Qt // doesn't have it's own: if ( !QTextCodec::codecForName("utf-7") ) { kdDebug(5006) << "No Qt-native utf-7 codec found; registering QUtf7Codec from libkdenetwork" << endl; (void) new QUtf7Codec(); } // In the case of Japan. Japanese locale name is "eucjp" but // The Japanese mail systems normally used "iso-2022-jp" of locale name. // We want to change locale name from eucjp to iso-2022-jp at KMail only. if ( QCString(QTextCodec::codecForLocale()->name()).lower() == "eucjp" ) { netCodec = QTextCodec::codecForName("jis7"); // QTextCodec *cdc = QTextCodec::codecForName("jis7"); // QTextCodec::setCodecForLocale(cdc); // KGlobal::locale()->setEncoding(cdc->mibEnum()); } else { netCodec = QTextCodec::codecForLocale(); } } KMKernel::~KMKernel () { delete mSystemTray; mSystemTray = 0; QMap::Iterator it = mPutJobs.begin(); while ( it != mPutJobs.end() ) { KIO::Job *job = it.key(); mPutJobs.remove( it ); job->kill(); it = mPutJobs.begin(); } mySelf = 0; kdDebug(5006) << "KMKernel::~KMKernel" << endl; } /********************************************************************/ /* DCOP-callable, and command line actions */ /********************************************************************/ void KMKernel::checkMail () //might create a new reader but won't show!! { kernel->acctMgr()->checkMail(false); } QStringList KMKernel::accounts() { return kernel->acctMgr()->getAccounts(); } void KMKernel::checkAccount (const QString &account) //might create a new reader but won't show!! { kdDebug(5006) << "KMKernel::checkMail called" << endl; KMAccount* acct = kernel->acctMgr()->find(account); if (acct) kernel->acctMgr()->singleCheckMail(acct, false); } void KMKernel::openReader() { KMMainWin *mWin = 0; KMainWindow *ktmw = 0; kdDebug(5006) << "KMKernel::openReader called" << endl; if (KMainWindow::memberList) for (ktmw = KMainWindow::memberList->first(); ktmw; ktmw = KMainWindow::memberList->next()) if (ktmw->isA("KMMainWin")) break; if (ktmw) { mWin = (KMMainWin *) ktmw; mWin->show(); KWin::setActiveWindow(mWin->winId()); } else { mWin = new KMMainWin; mWin->show(); } } int KMKernel::openComposer (const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, int hidden, const KURL &messageFile, const KURL::List &attachURLs) { kdDebug(5006) << "KMKernel::openComposer called" << endl; KMMessage *msg = new KMMessage; msg->initHeader(); msg->setCharset("utf-8"); if (!cc.isEmpty()) msg->setCc(cc); if (!bcc.isEmpty()) msg->setBcc(bcc); if (!subject.isEmpty()) msg->setSubject(subject); if (!to.isEmpty()) msg->setTo(to); if (!messageFile.isEmpty() && messageFile.isLocalFile()) { QCString str = kFileToString( messageFile.path(), true, false ); if( !str.isEmpty() ) msg->setBody( str ); } if (!body.isEmpty()) msg->setBody(body.utf8()); KMComposeWin *cWin = new KMComposeWin(msg); cWin->setCharset("", TRUE); for ( KURL::List::ConstIterator it = attachURLs.begin() ; it != attachURLs.end() ; ++it ) cWin->addAttach((*it)); if (hidden == 0) cWin->show(); return 1; } int KMKernel::openComposer (const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, int hidden, const QString &attachName, const QCString &attachCte, const QCString &attachData, const QCString &attachType, const QCString &attachSubType, const QCString &attachParamAttr, const QString &attachParamValue, const QCString &attachContDisp) { kdDebug(5006) << "KMKernel::openComposer called" << endl; KMMessage *msg = new KMMessage; msg->initHeader(); msg->setCharset("utf-8"); if (!cc.isEmpty()) msg->setCc(cc); if (!bcc.isEmpty()) msg->setBcc(bcc); if (!subject.isEmpty()) msg->setSubject(subject); if (!to.isEmpty()) msg->setTo(to); if (!body.isEmpty()) msg->setBody(body.utf8()); KMComposeWin *cWin = new KMComposeWin(msg); cWin->setCharset("", TRUE); if (!attachData.isEmpty()) { KMMessagePart *msgPart = new KMMessagePart; msgPart->setName(attachName); msgPart->setCteStr(attachCte); msgPart->setBodyEncoded(attachData); msgPart->setTypeStr(attachType); msgPart->setSubtypeStr(attachSubType); msgPart->setParameter(attachParamAttr,attachParamValue); msgPart->setContentDisposition(attachContDisp); cWin->addAttach(msgPart); } if (hidden == 0) cWin->show(); return 1; } DCOPRef KMKernel::openComposer(const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body,bool hidden) { KMMessage *msg = new KMMessage; msg->initHeader(); msg->setCharset("utf-8"); if (!cc.isEmpty()) msg->setCc(cc); if (!bcc.isEmpty()) msg->setBcc(bcc); if (!subject.isEmpty()) msg->setSubject(subject); if (!to.isEmpty()) msg->setTo(to); if (!body.isEmpty()) msg->setBody(body.utf8()); KMComposeWin *cWin = new KMComposeWin(msg); cWin->setCharset("", TRUE); if (!hidden) cWin->show(); return DCOPRef(cWin); } void KMKernel::toggleSystray(bool enabled, int mode) { kdDebug(5006) << "setupSystray called" << endl; if (enabled && !mSystemTray) { mSystemTray = new KMSystemTray(); } else if (!enabled && mSystemTray) { /** Get rid of system tray on user's request */ kdDebug(5006) << "deleting systray" << endl; delete mSystemTray; mSystemTray = 0; } /** Set mode of systemtray. If mode has changed, tray will handle this */ if(mSystemTray) { kdDebug(5006) << "Setting system tray mode" << endl; mSystemTray->setMode(mode); } } int KMKernel::sendCertificate( const QString& to, const QByteArray& certData ) { KMMessage *msg = new KMMessage; msg->initHeader(); msg->setCharset("utf-8"); msg->setSubject( i18n( "Certificate Signature Request" ) ); if (!to.isEmpty()) msg->setTo(to); msg->setBody( i18n( "Please sign this certificate and return to sender." ).utf8() ); KMComposeWin *cWin = new KMComposeWin(msg); cWin->setCharset("", TRUE); cWin->slotSetAlwaysSend( true ); if (!certData.isEmpty()) { KMMessagePart *msgPart = new KMMessagePart; msgPart->setName("smime.p10"); msgPart->setCteStr("base64"); msgPart->setBodyEncodedBinary(certData); msgPart->setTypeStr("application"); msgPart->setSubtypeStr("pkcs10"); msgPart->setContentDisposition("attachment; filename=smime.p10"); cWin->addAttach(msgPart); } cWin->show(); return 1; } void KMKernel::compactAllFolders () { kdDebug(5006) << "KMKernel::compactAllFolders called" << endl; the_folderMgr->compactAll(); kdDebug(5006) << "KMKernel::compactAllFolders finished" << endl; } int KMKernel::dcopAddMessage(const QString & foldername,const QString & msgUrlString) { return dcopAddMessage(foldername, KURL(msgUrlString)); } int KMKernel::dcopAddMessage(const QString & foldername,const KURL & msgUrl) { int retval; QCString bericht; static QStringList *msgIds=0; static QString lastFolder=""; bool readFolderMsgIds=false; //kdDebug(5006) << "KMKernel::dcopAddMessage called" << endl; if (foldername!=lastFolder) { if (msgIds!=0) { delete msgIds; msgIds = 0; } msgIds=new QStringList; readFolderMsgIds=true; lastFolder=foldername; } if (!msgUrl.isEmpty() && msgUrl.isLocalFile()) { // This is a proposed change by Daniel Andor. // He proposed to change from the fopen(blah) // to a kFileToString(blah). // Although it assigns a QString to a QString, // because of the implicit sharing this poses // no memory or performance penalty. bericht=kFileToString(msgUrl.path(),true,false); if (bericht.isNull()) { return -2; } KMMessage *M=new KMMessage(); M->fromString(bericht); KMFolder *F=the_folderMgr->findOrCreate(foldername, FALSE); if (F==0) { retval=-1; } else { if (readFolderMsgIds) {int i; // Try to determine if a message already exists in // the folder. The message id that is searched for, is // the subject line + the date. This should be quite // unique. The change that a given date with a given // subject is in the folder twice is very small. // If the subject is empty, the fromStrip string // is taken. F->open(); for(i=0;icount();i++) {KMMsgBase *mb=F->getMsgBase(i); time_t DT=mb->date(); QString dt=ctime(&DT); QString id=mb->subject(); if (id.isEmpty()) { id=mb->fromStrip(); } if (id.isEmpty()) { id=mb->toStrip(); } id+=dt; //fprintf(stderr,"%s\n",(const char *) id); if (!id.isEmpty()) { msgIds->append(id); } } F->close(); } time_t DT=M->date(); QString dt=ctime(&DT); QString msgId=M->subject(); if (msgId.isEmpty()) { msgId=M->fromStrip(); } if (msgId.isEmpty()) { msgId=M->toStrip(); } msgId+=dt; int k=msgIds->findIndex(msgId); //fprintf(stderr,"find %s = %d\n",(const char *) msgId,k); if (k==-1) { if (!msgId.isEmpty()) { msgIds->append(msgId); } if (F->addMsg(M)==0) { retval=1; } else { retval=-2;delete M; M = 0; } } else { retval=-4; } } return retval; } else { return -2; } } void KMKernel::requestAddresses( QString filename ) { mGroupware->requestAddresses( filename ); } bool KMKernel::lockContactsFolder() { return mGroupware->lockContactsFolder(); } bool KMKernel::unlockContactsFolder() { return mGroupware->unlockContactsFolder(); } bool KMKernel::storeAddresses( QString addresses, QStringList delUIDs ) { return mGroupware->storeAddresses( addresses, delUIDs ); } /********************************************************************/ /* Kernel methods */ /********************************************************************/ void KMKernel::quit() { // Called when all windows are closed. Will take care of compacting, // sending... should handle session management too!! } /* TODO later: Asuming that: - msgsender is nonblocking (our own, QSocketNotifier based. Pops up errors and sends signal senderFinished when done) - compacting is non blocking (insert processEvents there) o If we are getting mail, stop it (but don´t lose something!) o If we are sending mail, go on UNLESS this was called by SM, in which case stop ASAP that too (can we warn? should we continue on next start?) o If we are compacting, or expunging, go on UNLESS this was SM call. In that case stop compacting ASAP and continue on next start, before touching any folders. KMKernel::quit () { SM call? if compacting, stop; if sending, stop; if receiving, stop; Windows will take care of themselves (composer should dump it´s messages, if any but not in deadMail) declare us ready for the End of the Session No, normal quit call All windows are off. Anything to do, should compact or sender sends? Yes, maybe put an icon in panel as a sign of life Folder manager, go compacting (*except* outbox and sent-mail!) if sender sending, connect us to his finished slot, declare us ready for quit and wait for senderFinished if not, Folder manager, go compact sent-mail and outbox } (= call slotFinished()) void KMKernel::slotSenderFinished() { good, Folder manager go compact sent-mail and outbox clean up stage1 (release folders and config, unregister from dcop) -- another kmail may start now --- kapp->quit(); } void KMKernel:: void KMKernel:: */ /********************************************************************/ /* Init, Exit, and handler methods */ /********************************************************************/ void KMKernel::testDir(const char *_name) { // FIXME: use Qt methods (QFile, QDir) DIR *dp; QCString c( getenv("HOME") ); if(c.isEmpty()) { KMessageBox::sorry(0, i18n("$HOME is not set!\n" "KMail cannot start without it.\n")); ::exit(-1); } c += _name; dp = opendir(c); if (dp == 0) ::mkdir(c, S_IRWXU); else closedir(dp); } //----------------------------------------------------------------------------- // Open a composer for each message found in ~/dead.letter //to control void KMKernel::recoverDeadLetters(void) { KMComposeWin* win; KMMessage* msg; QDir dir = QDir::home(); QString fname = dir.path(); int i, rc, num; mDeadLetterTimer = new QTimer(this); connect(mDeadLetterTimer, SIGNAL(timeout()), this, SLOT(dumpDeadLetters())); if (!dir.exists("dead.letter")) { mDeadLetterTimer->start(mDeadLetterInterval); return; } fname += "/dead.letter"; KMFolderMbox folder(0, fname); folder.setAutoCreateIndex(FALSE); rc = folder.open(); if (rc) { perror(QString("cannot open file "+fname).latin1()); mDeadLetterTimer->start(mDeadLetterInterval); return; } folder.quiet(TRUE); folder.open(); num = folder.count(); for (i=0; isetMsg(msg, FALSE); win->show(); } } folder.close(); QFile::remove(fname); mDeadLetterTimer->start(mDeadLetterInterval); } void KMKernel::initFolders(KConfig* cfg) { QString name; name = cfg->readEntry("inboxFolder"); // Currently the folder manager cannot manage folders which are not // in the base folder directory. //if (name.isEmpty()) name = getenv("MAIL"); if (name.isEmpty()) name = "inbox"; the_inboxFolder = (KMFolder*)the_folderMgr->findOrCreate(name); if (the_inboxFolder->canAccess() != 0) { emergencyExit( i18n("You do not have read/write permission to your inbox folder.") ); } the_inboxFolder->setSystemFolder(TRUE); // inboxFolder->open(); the_outboxFolder = the_folderMgr->findOrCreate(cfg->readEntry("outboxFolder", "outbox")); if (the_outboxFolder->canAccess() != 0) { emergencyExit( i18n("You do not have read/write permission to your outbox folder.") ); } the_outboxFolder->setType("Out"); the_outboxFolder->setSystemFolder(TRUE); the_outboxFolder->open(); the_sentFolder = the_folderMgr->findOrCreate(cfg->readEntry("sentFolder", "sent-mail")); if (the_sentFolder->canAccess() != 0) { emergencyExit( i18n("You do not have read/write permission to your sent-mail folder.") ); } the_sentFolder->setType("St"); the_sentFolder->setSystemFolder(TRUE); // the_sentFolder->open(); the_trashFolder = the_folderMgr->findOrCreate(cfg->readEntry("trashFolder", "trash")); if (the_trashFolder->canAccess() != 0) { emergencyExit( i18n("You do not have read/write permission to your trash folder.") ); } the_trashFolder->setType("Tr"); the_trashFolder->setSystemFolder(TRUE); // the_trashFolder->open(); the_draftsFolder = the_folderMgr->findOrCreate(cfg->readEntry("draftsFolder", "drafts")); if (the_draftsFolder->canAccess() != 0) { emergencyExit( i18n("You do not have read/write permission to your drafts folder.") ); } the_draftsFolder->setType("Df"); the_draftsFolder->setSystemFolder(TRUE); the_draftsFolder->open(); } void KMKernel::init() { QString foldersPath; KConfig* cfg; the_shuttingDown = false; the_server_is_ready = false; the_kbp = new KBusyPtr; cfg = KMKernel::config(); mCryptPlugList.loadFromConfig( cfg ); QDir dir; QString d = locateLocal("data", "kmail/"); KConfigGroupSaver saver(cfg, "General"); the_firstStart = cfg->readBoolEntry("first-start", true); cfg->writeEntry("first-start", false); the_previousVersion = cfg->readEntry("previous-version", ""); cfg->writeEntry("previous-version", KMAIL_VERSION); foldersPath = cfg->readEntry("folders", ""); if (foldersPath.isEmpty()) { foldersPath = QDir::homeDirPath() + QString("/Mail"); transferMail(); } the_undoStack = new KMUndoStack(20); the_folderMgr = new KMFolderMgr(foldersPath); the_imapFolderMgr = new KMFolderMgr(locateLocal("data","kmail/imap"), KMImapDir); the_searchFolderMgr = new KMFolderMgr(locateLocal("data","kmail/search"), KMSearchDir); the_acctMgr = new KMAcctMgr(); the_filterMgr = new KMFilterMgr(); the_popFilterMgr = new KMFilterMgr(true); the_filterActionDict = new KMFilterActionDict; + // moved up here because KMMessage::stripOffPrefixes is used below -ta + KMMessage::readConfig(); initFolders(cfg); the_acctMgr->readConfig(); the_filterMgr->readConfig(); the_popFilterMgr->readConfig(); cleanupImapFolders(); - KMMessage::readConfig(); the_msgSender = new KMSender; the_server_is_ready = true; { // area for config group "Composer" KConfigGroupSaver saver(cfg, "Composer"); if (cfg->readListEntry("pref-charsets").isEmpty()) { cfg->writeEntry("pref-charsets", "us-ascii,iso-8859-1,locale,utf-8"); } } mGroupware->readConfigStartup(); // filterMgr->dump(); #if 0 //disabled for now.. the_msgIndex = new KMMsgIndex(this, "the_index"); //create the indexer #endif } void KMKernel::cleanupImapFolders() { KMAccount *acct; KMFolderNode *node = the_imapFolderMgr->dir().first(); while (node) { if (node->isDir() || ((acct = the_acctMgr->find(node->name())) && ( acct->type() == "imap" || acct->type() == "cachedimap" )) ) { node = the_imapFolderMgr->dir().next(); } else { the_imapFolderMgr->remove(static_cast(node)); node = the_imapFolderMgr->dir().first(); } } the_imapFolderMgr->quiet(TRUE); for (acct = the_acctMgr->first(); acct; acct = the_acctMgr->next()) { KMFolderImap *fld; KMAcctImap *imapAcct; if (acct->type() != "imap") continue; fld = static_cast(the_imapFolderMgr ->findOrCreate(acct->name(), FALSE)); fld->setNoContent(TRUE); imapAcct = static_cast(acct); fld->setAccount(imapAcct); imapAcct->setImapFolder(fld); fld->close(); } for (acct = the_acctMgr->first(); acct; acct = the_acctMgr->next()) { KMFolderCachedImap *cfld; KMAcctCachedImap *cachedImapAcct; if (acct->type() != "cachedimap" ) continue; kdDebug(5006) << "findorCreating " << acct->name() << endl; cfld = static_cast(the_imapFolderMgr->find(acct->name())); if (cfld == 0) { // Folder doesn't exist yet cfld = static_cast(the_imapFolderMgr->createFolder(acct->name(), FALSE, KMFolderTypeCachedImap)); if (!cfld) { KMessageBox::error(0,(i18n("Cannot create file `%1' in %2.\nKMail cannot start without it.").arg(acct->name()).arg(the_imapFolderMgr->basePath()))); exit(-1); } } //cfld->setNoContent(TRUE); cachedImapAcct = static_cast(acct); cfld->setAccount(cachedImapAcct); cachedImapAcct->setImapFolder(cfld); cfld->close(); } the_imapFolderMgr->quiet(FALSE); } bool KMKernel::doSessionManagement() { // Do session management if (kapp->isRestored()){ int n = 1; while (KMMainWin::canBeRestored(n)){ //only restore main windows! (Matthias); if (KMMainWin::classNameOfToplevel(n) == "KMMainWin") (new KMMainWin)->restore(n); n++; } return true; // we were restored by SM } return false; // no, we were not restored } void KMKernel::cleanup(void) { dumpDeadLetters(); mDeadLetterTimer->stop(); the_shuttingDown = TRUE; delete the_acctMgr; the_acctMgr = 0; delete the_filterMgr; the_filterMgr = 0; delete the_msgSender; the_msgSender = 0; delete the_filterActionDict; the_filterActionDict = 0; delete the_undoStack; the_undoStack = 0; delete the_popFilterMgr; the_popFilterMgr = 0; // Since the application has already quit we can't use // kapp->processEvents() because it will return immediately: // We first have to fire up a new event loop. // We use the timer to transfer control to the cleanupLoop function // once the event loop is running. // Don't handle DCOP requests from the event loop kapp->dcopClient()->suspend(); // Schedule execution of cleanupLoop QTimer::singleShot(0, this, SLOT(cleanupLoop())); // Start new event loop kapp->enter_loop(); } void KMKernel::cleanupProgress() { mProgress->advance( 1 ); } void KMKernel::cleanupLoop() { QStringList cleanupMsgs; cleanupMsgs << i18n("Cleaning up...") << i18n("Emptying trash...") << i18n("Expiring old messages...") << i18n("Compacting folders..."); enum { CleaningUpMsgNo = 0, EmptyTrashMsgNo = 1, ExpiringOldMessagesMsgNo = 2, CompactingFoldersMsgNo = 3 }; mProgress = 0; mCleanupLabel = 0; mCleanupPopup = 0; int nrFolders = the_folderMgr->folderCount(); if (closed_by_user) { mCleanupPopup = new KPassivePopup(); QVBox *box = mCleanupPopup->standardView( kapp->aboutData()->programName(), QString::null, kapp->miniIcon()); mCleanupLabel = new QLabel( cleanupMsgs[CleaningUpMsgNo], box ); // determine the maximal width of the clean up messages QFontMetrics fm = mCleanupLabel->fontMetrics(); int maxTextWidth = 0; for( QStringList::ConstIterator it = cleanupMsgs.begin(); it != cleanupMsgs.end(); ++it ) { int w; if( maxTextWidth < ( w = fm.width( *it ) ) ) maxTextWidth = w; } mProgress = new KProgress( box, "kmail-cleanupProgress" ); mProgress->setMinimumWidth( maxTextWidth+20 ); mCleanupPopup->setView( box ); mProgress->setTotalSteps(nrFolders*2+2); mProgress->setProgress(1); QApplication::syncX(); mCleanupPopup->adjustSize(); mCleanupPopup->show(); kapp->processEvents(); connect(the_folderMgr, SIGNAL(progress()), this, SLOT(cleanupProgress())); } KConfig* config = KMKernel::config(); KConfigGroupSaver saver(config, "General"); bool expire = false; // Expire old messages in all folders. if (closed_by_user) { if (config->readNumEntry("when-to-expire")==expireAtExit) { expire = true; if (config->readBoolEntry("warn-before-expire")) { expire = canExpire(); } } } if (!closed_by_user) { if (the_trashFolder) the_trashFolder->close(); } else if (the_trashFolder) { the_trashFolder->close(TRUE); if (config->readBoolEntry("empty-trash-on-exit", true)) { if (mCleanupLabel) { mCleanupLabel->setText( cleanupMsgs[EmptyTrashMsgNo] ); QApplication::syncX(); kapp->processEvents(); } the_trashFolder->expunge(); } } if (mProgress) mProgress->setProgress(2); if (expire) { if (mCleanupLabel) { mCleanupLabel->setText( cleanupMsgs[ExpiringOldMessagesMsgNo] ); QApplication::syncX(); kapp->processEvents(); } the_folderMgr->expireAllFolders(0); } if (mProgress) mProgress->setProgress(2+nrFolders); if (closed_by_user && the_folderMgr) { if (config->readBoolEntry("compact-all-on-exit", true)) { if (mCleanupLabel) { mCleanupLabel->setText( cleanupMsgs[CompactingFoldersMsgNo] ); QApplication::syncX(); kapp->processEvents(); } the_folderMgr->compactAll(); // I can compact for ages in peace now! } } if (mProgress) { mCleanupLabel->setText( cleanupMsgs[CleaningUpMsgNo] ); mProgress->setProgress(2+2*nrFolders); QApplication::syncX(); kapp->processEvents(); } if (the_inboxFolder) the_inboxFolder->close(TRUE); if (the_outboxFolder) the_outboxFolder->close(TRUE); if (the_sentFolder) the_sentFolder->close(TRUE); if (the_draftsFolder) the_draftsFolder->close(TRUE); mGroupware->cleanup(); folderMgr()->writeMsgDict(msgDict()); imapFolderMgr()->writeMsgDict(msgDict()); QValueList > folders; QStringList strList; KMFolder *folder; the_searchFolderMgr->createFolderList(&strList, &folders); for (int i = 0; folders.at(i) != folders.end(); i++) { folder = *folders.at(i); if (!folder || folder->isDir()) continue; folder->close(TRUE); } delete the_msgIndex; the_msgIndex = 0; delete the_folderMgr; the_folderMgr = 0; delete the_imapFolderMgr; the_imapFolderMgr = 0; delete the_searchFolderMgr; the_searchFolderMgr = 0; delete the_msgDict; the_msgDict = 0; delete the_kbp; the_kbp = 0; delete mConfigureDialog; mConfigureDialog = 0; delete mWin; mWin = 0; //qInstallMsgHandler(oldMsgHandler); RecentAddresses::self()->save( KMKernel::config() ); KMKernel::config()->sync(); if (mCleanupPopup) { sleep(1); // Give the user some time to realize what's going on delete mCleanupPopup; mCleanupPopup = 0; mCleanupLabel = 0; // auto-deleted child of mCleanupPopup mProgress = 0; // ditto } kapp->exit_loop(); } //Isn´t this obsolete? (sven) void KMKernel::transferMail(void) { QDir dir = QDir::home(); int rc; // Stefan: This function is for all the whiners who think that KMail is // broken because they cannot read mail with pine and do not // know how to fix this problem with a simple symbolic link =;-) // Markus: lol ;-) if (!dir.cd("KMail")) return; rc = KMessageBox::questionYesNo(0, i18n( "The directory ~/KMail exists. From now on, KMail uses the " "directory ~/Mail for its messages.\n" "KMail can move the contents of the directory ~/KMail into " "~/Mail, but this will replace existing files with the same " "name in the directory ~/Mail (e.g. inbox).\n" "Should KMail move the mail folders now?")); if (rc == KMessageBox::No) return; dir.cd("/"); // otherwise we lock the directory testDir("/Mail"); system("mv -f ~/KMail/* ~/Mail"); system("mv -f ~/KMail/.??* ~/Mail"); system("rmdir ~/KMail"); } void KMKernel::ungrabPtrKb(void) { if(!KMainWindow::memberList) return; QWidget* widg = KMainWindow::memberList->first(); Display* dpy; if (!widg) return; dpy = widg->x11Display(); XUngrabKeyboard(dpy, CurrentTime); XUngrabPointer(dpy, CurrentTime); } // Message handler void KMKernel::kmailMsgHandler(QtMsgType aType, const char* aMsg) { static int recurse=-1; recurse++; switch (aType) { case QtDebugMsg: case QtWarningMsg: kdDebug(5006) << aMsg << endl;; break; case QtFatalMsg: // Hm, what about using kdFatal() here? ungrabPtrKb(); kdDebug(5006) << kapp->caption() << " fatal error " << aMsg << endl; KMessageBox::error(0, aMsg); abort(); } recurse--; } void KMKernel::dumpDeadLetters() { mDeadLetterTimer->stop(); QWidget *win; QPtrListIterator it(*KMainWindow::memberList); QDir dir = QDir::home(); QString fname = dir.path(); QFile::remove(fname + "/dead.letter.tmp"); while ((win = it.current()) != 0) { ++it; if (win->inherits("KMComposeWin")) ((KMComposeWin*)win)->deadLetter(); // delete win; // WABA: Don't delete, we might crash in there! } QFile::remove(fname + "/dead.letter"); dir.rename("dead.letter.tmp","dead.letter"); mDeadLetterTimer->start(mDeadLetterInterval); } void KMKernel::action(bool mailto, bool check, const QString &to, const QString &cc, const QString &bcc, const QString &subj, const QString &body, const KURL &messageFile, const KURL::List &attachURLs) { if (mailto) openComposer (to, cc, bcc, subj, body, 0, messageFile, attachURLs); else openReader(); if (check) checkMail(); //Anything else? } void KMKernel::byteArrayToRemoteFile(const QByteArray &aData, const KURL &aURL, bool overwrite) { KIO::Job *job = KIO::put(aURL, -1, overwrite, FALSE); putData pd; pd.url = aURL; pd.data = aData; pd.offset = 0; mPutJobs.insert(job, pd); connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)), SLOT(slotDataReq(KIO::Job*,QByteArray&))); connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotResult(KIO::Job*))); } void KMKernel::slotDataReq(KIO::Job *job, QByteArray &data) { // send the data in 64 KB chunks const int MAX_CHUNK_SIZE = 64*1024; QMap::Iterator it = mPutJobs.find(job); assert(it != mPutJobs.end()); int remainingBytes = (*it).data.size() - (*it).offset; if( remainingBytes > MAX_CHUNK_SIZE ) { // send MAX_CHUNK_SIZE bytes to the receiver (deep copy) data.duplicate( (*it).data.data() + (*it).offset, MAX_CHUNK_SIZE ); (*it).offset += MAX_CHUNK_SIZE; //kdDebug( 5006 ) << "Sending " << MAX_CHUNK_SIZE << " bytes (" // << remainingBytes - MAX_CHUNK_SIZE << " bytes remain)\n"; } else { // send the remaining bytes to the receiver (deep copy) data.duplicate( (*it).data.data() + (*it).offset, remainingBytes ); (*it).data = QByteArray(); (*it).offset = 0; //kdDebug( 5006 ) << "Sending " << remainingBytes << " bytes\n"; } } void KMKernel::slotResult(KIO::Job *job) { QMap::Iterator it = mPutJobs.find(job); assert(it != mPutJobs.end()); if (job->error()) { if (job->error() == KIO::ERR_FILE_ALREADY_EXIST) { if (KMessageBox::warningContinueCancel(0, i18n("File %1 exists.\nDo you want to replace it?") .arg((*it).url.prettyURL()), i18n("Save to file"), i18n("&Replace")) == KMessageBox::Continue) byteArrayToRemoteFile((*it).data, (*it).url, TRUE); } else job->showErrorDialog(); } mPutJobs.remove(it); } void KMKernel::slotCollectStdOut( KProcess * proc, char * buffer, int len ) { QByteArray & ba = mStdOutCollection[proc]; // append data to ba: int oldsize = ba.size(); ba.resize( oldsize + len ); qmemmove( ba.begin() + oldsize, buffer, len ); } void KMKernel::slotCollectStdErr( KProcess * proc, char * buffer, int len ) { QByteArray & ba = mStdErrCollection[proc]; // append data to ba: int oldsize = ba.size(); ba.resize( oldsize + len ); qmemmove( ba.begin() + oldsize, buffer, len ); } QByteArray KMKernel::getCollectedStdOut( KProcess * proc ) { QByteArray result = mStdOutCollection[proc]; mStdOutCollection.remove(proc); return result; } QByteArray KMKernel::getCollectedStdErr( KProcess * proc ) { QByteArray result = mStdErrCollection[proc]; mStdErrCollection.remove(proc); return result; } void KMKernel::slotRequestConfigSync() { // ### FIXME: delay as promised in the kdoc of this function ;-) KMKernel::config()->sync(); } void KMKernel::slotShowConfigurationDialog() { if( !mConfigureDialog ) mConfigureDialog = new ConfigureDialog( 0, "configure", false ); if( mConfigureDialog->isHidden() ) mConfigureDialog->show(); else mConfigureDialog->raise(); } void KMKernel::notClosedByUser() { closed_by_user = false; delete the_acctMgr; the_acctMgr = 0; delete the_filterMgr; the_filterMgr = 0; delete the_msgSender; the_msgSender = 0; delete the_filterActionDict; the_filterActionDict = 0; delete the_undoStack; the_undoStack = 0; delete the_popFilterMgr; the_popFilterMgr = 0; QStringList strList; QValueList > folders; KMFolder *folder; the_folderMgr->createFolderList(&strList, &folders); for (int i = 0; folders.at(i) != folders.end(); i++) { folder = *folders.at(i); if (!folder || folder->isDir()) continue; folder->close(TRUE); } strList.clear(); folders.clear(); the_searchFolderMgr->createFolderList(&strList, &folders); for (int i = 0; folders.at(i) != folders.end(); i++) { folder = *folders.at(i); if (!folder || folder->isDir()) continue; folder->close(TRUE); } folderMgr()->writeMsgDict(msgDict()); imapFolderMgr()->writeMsgDict(msgDict()); delete the_msgIndex; the_msgIndex = 0; delete the_folderMgr; the_folderMgr = 0; delete the_imapFolderMgr; the_imapFolderMgr = 0; delete the_searchFolderMgr; the_searchFolderMgr = 0; delete the_msgDict; the_msgDict = 0; delete the_kbp; the_kbp = 0; delete mConfigureDialog; mConfigureDialog = 0; delete mWin; mWin = 0; } void KMKernel::emergencyExit( const QString& reason ) { QString mesg = i18n("KMail encountered a fatal error and will " "terminate now.\nThe error was:\n%1").arg( reason ); kdWarning() << mesg << endl; KNotifyClient::userEvent( mesg, KNotifyClient::Messagebox, KNotifyClient::Error ); ::exit(1); } /** * Sets whether the user wants to expire old email on exit. * When last main window is closed, user can be presented with * a dialog asking them whether they want to expire old email. * This is used to keep track of the answer for when cleanup * occurs. */ void KMKernel::setCanExpire(bool expire) { allowedToExpire = expire; } /** * Returns true or false depending on whether user has given * their consent to old email expiry for this session. */ bool KMKernel::canExpire() { return allowedToExpire; } /** * Returns true if the folder is either the outbox or one of the drafts-folders */ bool KMKernel::folderIsDraftOrOutbox(const KMFolder * folder) { assert( folder ); if ( folder == the_outboxFolder || folder == the_draftsFolder ) return true; QString idString = folder->idString(); if ( idString.isEmpty() ) return false; // search the identities if the folder matches the drafts-folder const IdentityManager * im = identityManager(); for( IdentityManager::ConstIterator it = im->begin(); it != im->end(); ++it ) if ( (*it).drafts() == idString ) return true; return false; } bool KMKernel::folderIsTrash(KMFolder * folder) { assert(folder); if (folder == the_trashFolder) return true; if (folder->protocol() != "imap") return false; KMFolderImap *fi = static_cast(folder); if (fi->account()->trash() == fi->idString()) return true; return false; } IdentityManager * KMKernel::identityManager() { if ( !mIdentityManager ) { kdDebug(5006) << "instantating IdentityManager" << endl; mIdentityManager = new IdentityManager( this, "mIdentityManager" ); } return mIdentityManager; } KMMsgDict *KMKernel::msgDict() { if (the_msgDict) return the_msgDict; the_msgDict = new KMMsgDict; folderMgr()->readMsgDict(msgDict()); imapFolderMgr()->readMsgDict(msgDict()); return the_msgDict; } KMMsgIndex *KMKernel::msgIndex() { return the_msgIndex; } KMMainWin* KMKernel::mainWin() { KMainWindow *kmWin = 0; for (kmWin = KMainWindow::memberList->first(); kmWin; kmWin = KMainWindow::memberList->next()) if (kmWin->isA("KMMainWin")) break; if (kmWin && kmWin->isA("KMMainWin")) { return (KMMainWin *) kmWin; } else { mWin = new KMMainWin; return mWin; } } /** * Emptyies al the trash folders */ void KMKernel::slotEmptyTrash() { QString title = i18n("Empty Trash"); QString text = i18n("Are you sure you want to empty the trash?"); if (KMessageBox::warningContinueCancel(0, text, title, KStdGuiItem::cont(), "confirm_empty_trash") != KMessageBox::Continue) { return; } for (KMAccount* acct = acctMgr()->first(); acct; acct = acctMgr()->next()) { KMFolder* trash = folderMgr()->findIdString(acct->trash()); if (trash) { trash->expunge(); } } } KConfig *KMKernel::myConfig = 0; static KStaticDeleter myConfigSD; KConfig* KMKernel::config() { if (!myConfig) myConfig = myConfigSD.setObject(new KConfig( "kmailrc")); return myConfig; } KMGroupware & KMKernel::groupware() { assert( mGroupware ); return *mGroupware; } #include "kmkernel.moc" diff --git a/kmail/kmmessage.cpp b/kmail/kmmessage.cpp index ca4e2b36e5..745d1a63e8 100644 --- a/kmail/kmmessage.cpp +++ b/kmail/kmmessage.cpp @@ -1,3706 +1,3754 @@ // kmmessage.cpp // if you do not want GUI elements in here then set ALLOW_GUI to 0. #include #define ALLOW_GUI 1 #include "kmmessage.h" #include "kmmsgpart.h" #include "kmreaderwin.h" #include "mailinglist-magic.h" #include "objecttreeparser.h" using KMail::ObjectTreeParser; #include #include #include #include #include "kmfolderindex.h" #include "kmundostack.h" #include "kmversion.h" #include "kmidentity.h" #include "kmkernel.h" #include "identitymanager.h" #include "headerstrategy.h" using KMail::HeaderStrategy; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ALLOW_GUI #include #include #endif // needed temporarily until KMime is replacing the partNode helper class: #include "partNode.h" using namespace KMime; static DwString emptyString(""); // Values that are set from the config file with KMMessage::readConfig() static QString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr; static bool sSmartQuote, sReplaceSubjPrefix, sReplaceForwSubjPrefix; static int sWrapCol; static QStringList sReplySubjPrefixes, sForwardSubjPrefixes; static QStringList sPrefCharsets; QString KMMessage::sForwardStr = ""; const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich(); //----------------------------------------------------------------------------- KMMessage::KMMessage(DwMessage* aMsg) : mMsg(aMsg), mNeedsAssembly(true), mIsComplete(false), mDecodeHTML(false), mTransferInProgress(0), mCodec(0), mUnencryptedMsg(0) { } //----------------------------------------------------------------------------- KMMessage::KMMessage(const KMMessage& other) : KMMessageInherited( other ), mMsg(0) { mUnencryptedMsg = 0; assign( other ); } void KMMessage::assign( const KMMessage& other ) { delete mMsg; delete mUnencryptedMsg; mNeedsAssembly = true;//other.mNeedsAssembly; if( other.mMsg ) mMsg = new DwMessage( *(other.mMsg) ); mCodec = other.mCodec; mDecodeHTML = other.mDecodeHTML; mIsComplete = false;//other.mIsComplete; mTransferInProgress = other.mTransferInProgress; mMsgSize = other.mMsgSize; mMsgLength = other.mMsgLength; mFolderOffset = other.mFolderOffset; mStatus = other.mStatus; mEncryptionState = other.mEncryptionState; mSignatureState = other.mSignatureState; mMDNSentState = other.mMDNSentState; mDate = other.mDate; if( other.hasUnencryptedMsg() ) mUnencryptedMsg = new KMMessage( *other.unencryptedMsg() ); else mUnencryptedMsg = 0; //mFileName = ""; // we might not want to copy the other messages filename (?) //mMsgSerNum = other.mMsgSerNum; // what about serial number ? mMsgSerNum = 0; //KMMsgBase::assign( &other ); } //----------------------------------------------------------------------------- void KMMessage::setReferences(const QCString& aStr) { if (!aStr) return; mMsg->Headers().References().FromString(aStr); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- QCString KMMessage::id(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasMessageId()) return header.MessageId().AsString().c_str(); else return ""; } //----------------------------------------------------------------------------- unsigned long KMMessage::getMsgSerNum() const { if (mMsgSerNum) return mMsgSerNum; return KMMsgBase::getMsgSerNum(); } //----------------------------------------------------------------------------- void KMMessage::setMsgSerNum(unsigned long newMsgSerNum) { if (newMsgSerNum) mMsgSerNum = newMsgSerNum; else if (!mMsgSerNum) mMsgSerNum = getMsgSerNum(); } //----------------------------------------------------------------------------- KMMessage::KMMessage(KMFolderIndex* parent): KMMessageInherited(parent) { mNeedsAssembly = FALSE; mMsg = new DwMessage; mCodec = 0; mDecodeHTML = FALSE; mIsComplete = FALSE; mTransferInProgress = 0; mMsgSize = 0; mMsgLength = 0; mFolderOffset = 0; mStatus = KMMsgStatusNew; mEncryptionState = KMMsgEncryptionStateUnknown; mSignatureState = KMMsgSignatureStateUnknown; mMDNSentState = KMMsgMDNStateUnknown; mDate = 0; mFileName = ""; mMsgSerNum = 0; mUnencryptedMsg = 0; } //----------------------------------------------------------------------------- KMMessage::KMMessage(KMMsgInfo& msgInfo): KMMessageInherited() { mNeedsAssembly = FALSE; mMsg = new DwMessage; mCodec = 0; mDecodeHTML = FALSE; mIsComplete = FALSE; mTransferInProgress = 0; mMsgSize = msgInfo.msgSize(); mMsgLength = 0; mFolderOffset = msgInfo.folderOffset(); mStatus = msgInfo.status(); mEncryptionState = msgInfo.encryptionState(); mSignatureState = msgInfo.signatureState(); mMDNSentState = msgInfo.mdnSentState(); mDate = msgInfo.date(); mFileName = msgInfo.fileName(); mMsgSerNum = msgInfo.getMsgSerNum(); KMMsgBase::assign(&msgInfo); mUnencryptedMsg = 0; } //----------------------------------------------------------------------------- KMMessage::~KMMessage() { Q_ASSERT( !transferInProgress() ); delete mMsg; kernel->undoStack()->msgDestroyed( this ); } //----------------------------------------------------------------------------- bool KMMessage::isMessage(void) const { return TRUE; } bool KMMessage::isUrgent() const { return headerField( "Priority" ).contains( "urgent", false ) || headerField( "X-Priority" ).startsWith( "2" ); } //----------------------------------------------------------------------------- void KMMessage::setUnencryptedMsg( KMMessage* unencrypted ) { delete mUnencryptedMsg; mUnencryptedMsg = unencrypted; } //----------------------------------------------------------------------------- const DwString& KMMessage::asDwString() const { if (mNeedsAssembly) { mNeedsAssembly = FALSE; mMsg->Assemble(); } return mMsg->AsString(); } //----------------------------------------------------------------------------- const DwMessage *KMMessage::asDwMessage(void) { if (mNeedsAssembly) { mNeedsAssembly = FALSE; mMsg->Assemble(); } return mMsg; } //----------------------------------------------------------------------------- QCString KMMessage::asString() const { return asDwString().c_str(); } QCString KMMessage::asSendableString() const { KMMessage msg; msg.fromString(asString()); msg.removePrivateHeaderFields(); msg.removeHeaderField("Bcc"); return msg.asString(); } QCString KMMessage::headerAsSendableString() const { KMMessage msg; msg.fromString(asString()); msg.removePrivateHeaderFields(); msg.removeHeaderField("Bcc"); return msg.headerAsString().latin1(); } void KMMessage::removePrivateHeaderFields() { removeHeaderField("Status"); removeHeaderField("X-Status"); removeHeaderField("X-KMail-EncryptionState"); removeHeaderField("X-KMail-SignatureState"); removeHeaderField("X-KMail-MDN-Sent"); removeHeaderField("X-KMail-Transport"); removeHeaderField("X-KMail-Identity"); removeHeaderField("X-KMail-Fcc"); removeHeaderField("X-KMail-Redirect-From"); removeHeaderField("X-KMail-Link-Message"); removeHeaderField("X-KMail-Link-Type"); } //----------------------------------------------------------------------------- void KMMessage::setStatusFields(void) { char str[3]; str[0] = (char)status(); str[1] = '\0'; setHeaderField("Status", status()==KMMsgStatusNew ? "R " : "RO"); setHeaderField("X-Status", str); str[0] = (char)encryptionState(); str[1] = '\0'; setHeaderField("X-KMail-EncryptionState", str); str[0] = (char)signatureState(); str[1] = '\0'; //kdDebug(5006) << "Setting SignatureState header field to " << str[0] << endl; setHeaderField("X-KMail-SignatureState", str); str[0] = static_cast( mdnSentState() ); str[1] = '\0'; setHeaderField("X-KMail-MDN-Sent", str); // We better do the assembling ourselves now to prevent the // mimelib from changing the message *body*. (khz, 10.8.2002) mNeedsAssembly = false; mMsg->Headers().Assemble(); mMsg->Assemble( mMsg->Headers(), mMsg->Body() ); } //---------------------------------------------------------------------------- QString KMMessage::headerAsString(void) const { DwHeaders& header = mMsg->Headers(); header.Assemble(); if(header.AsString() != "") return header.AsString().c_str(); return ""; } //----------------------------------------------------------------------------- DwMediaType& KMMessage::dwContentType(void) { return mMsg->Headers().ContentType(); } //----------------------------------------------------------------------------- void KMMessage::fromString(const QCString& aStr, bool aSetStatus) { DwString dwStra( aStr.data() ); fromDwString( dwStra, aSetStatus ); return; } //----------------------------------------------------------------------------- void KMMessage::fromDwString(const DwString& str, bool aSetStatus) { const char* strPos = str.data(); char ch; bool needsJpDecode = false; delete mMsg; mMsg = new DwMessage; mMsgLength = str.length(); if (strPos) for (; strPos < str.data() + str.length(); ++strPos) { ch = *strPos; if (!((ch>=' ' || ch=='\t' || ch=='\n' || ch<='\0' || ch == 0x1b) && !(ch=='>' && strPos > str.data() && qstrncmp(strPos-1, "\n>From", 6) == 0))) { needsJpDecode = true; break; } } if (needsJpDecode) { // copy string and throw out obsolete control characters char *resultPos; int len = str.length(); char* rawData = new char[ len + 1 ]; QCString result; result.setRawData( rawData, len + 1 ); strPos = str.data(); resultPos = (char*)result.data(); if (strPos) for (; strPos < str.data() + str.length(); ++strPos) { ch = *strPos; // Mail header charset(iso-2022-jp) is using all most E-mail system in Japan. // ISO-2022-JP code consists of ESC(0x1b) character and 7Bit character which // used from '!' character to '~' character. toyo if ((ch>=' ' || ch=='\t' || ch=='\n' || ch<='\0' || ch == 0x1b) && !(ch=='>' && strPos > str.data() && qstrncmp(strPos-1, "\n>From", 6) == 0)) *resultPos++ = ch; } *resultPos = '\0'; // terminate zero for casting DwString jpStr; jpStr.TakeBuffer( result.data(), len + 1, 0, result.length() ); mMsg->FromString( jpStr ); result.resetRawData( result, len + 1); } else { mMsg->FromString( str ); } mMsg->Parse(); if (aSetStatus) { setStatus(headerField("Status").latin1(), headerField("X-Status").latin1()); setEncryptionStateChar( headerField("X-KMail-EncryptionState").at(0) ); setSignatureStateChar( headerField("X-KMail-SignatureState").at(0) ); setMDNSentState( static_cast( headerField("X-KMail-MDN-Sent").at(0).latin1() ) ); } mNeedsAssembly = FALSE; mDate = date(); // Convert messages with a binary body into a message with attachment. #if 0 QCString ct = dwContentType().TypeStr().c_str(); QCString st = dwContentType().SubtypeStr().c_str(); ct = ct.lower(); st = st.lower(); if ( ct.isEmpty() || ct == "text" || ct == "multipart" || ( ct == "application" && (st == "pkcs7-mime" || st == "x-pkcs7-mime" || st == "pgp") ) ) return; KMMessagePart textPart; textPart.setTypeStr("text"); textPart.setSubtypeStr("plain"); textPart.setBody("\n"); KMMessagePart bodyPart; bodyPart.setTypeStr(ct); bodyPart.setSubtypeStr(subtypeStr()); bodyPart.setContentDisposition(headerField("Content-Disposition").latin1()); bodyPart.setCteStr(contentTransferEncodingStr()); bodyPart.setContentDisposition(headerField("Content-Disposition").latin1()); bodyPart.setBodyEncodedBinary(bodyDecodedBinary()); addBodyPart(&textPart); addBodyPart(&bodyPart); mNeedsAssembly = FALSE; #endif } //----------------------------------------------------------------------------- QString KMMessage::formatString(const QString& aStr) const { QString result, str; QChar ch; uint j; if (aStr.isEmpty()) return aStr; for (uint i=0; i' '; j++) ; for (; j < str.length() && str[j] <= ' '; j++) ; result += str[0]; if (str[j]>' ') result += str[j]; else if (str[1]>' ') result += str[1]; break; case 'T': result += stripEmailAddr(to()); break; case 't': result += to(); break; case 'S': result += subject(); break; case '_': result += ' '; break; case 'L': result += "\n"; break; case '%': result += '%'; break; default: result += '%'; result += ch; break; } } else result += ch; } return result; } static void removeTrailingSpace( QString &line ) { int i = line.length()-1; while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t'))) i--; line.truncate( i+1); } static QString splitLine( QString &line) { removeTrailingSpace( line ); int i = 0; int j = -1; int l = line.length(); // TODO: Replace tabs with spaces first. while(i < l) { QChar c = line[i]; if ((c == '>') || (c == ':') || (c == '|')) j = i+1; else if ((c != ' ') && (c != '\t')) break; i++; } if ( j <= 0 ) { return ""; } if ( i == l ) { QString result = line.left(j); line = QString::null; return result; } QString result = line.left(j); line = line.mid(j); return result; } static QString flowText(QString &text, const QString& indent, int maxLength) { // printf("flowText: \"%s\"\n", text.ascii()); maxLength--; if (text.isEmpty()) { return indent+"\n"; } QString result; while (1) { int i; if ((int) text.length() > maxLength) { i = maxLength; while( (i >= 0) && (text[i] != ' ')) i--; if (i <= 0) { // Couldn't break before maxLength. i = maxLength; // while( (i < (int) text.length()) && (text[i] != ' ')) // i++; } } else { i = text.length(); } QString line = text.left(i); if (i < (int) text.length()) text = text.mid(i); else text = QString::null; result += indent + line + '\n'; if (text.isEmpty()) return result; } } static bool flushPart(QString &msg, QStringList &part, const QString &indent, int maxLength) { maxLength -= indent.length(); if (maxLength < 20) maxLength = 20; // Remove empty lines at end of quote while ((part.begin() != part.end()) && part.last().isEmpty()) { part.remove(part.fromLast()); } //printf("Start of part.\n"); QString text; for(QStringList::Iterator it2 = part.begin(); it2 != part.end(); it2++) { QString line = (*it2); if (line.isEmpty()) { if (!text.isEmpty()) msg += flowText(text, indent, maxLength); msg += indent + '\n'; } else { if (text.isEmpty()) text = line; else text += ' '+line.stripWhiteSpace(); if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10))) msg += flowText(text, indent, maxLength); } } if (!text.isEmpty()) msg += flowText(text, indent, maxLength); //printf("End of of part.\n"); bool appendEmptyLine = true; if (!part.count()) appendEmptyLine = false; part.clear(); return appendEmptyLine; } static void stripSignature(QString& msg, bool clearSigned) { if (clearSigned) { msg = msg.left(msg.findRev(QRegExp("\\n--\\s?\\n"))); } else { msg = msg.left(msg.findRev("\n-- \n")); } } static void smartQuote( QString &msg, int maxLength ) { QStringList part; QString oldIndent; bool firstPart = true; //printf("Smart Quoting.\n"); QStringList lines = QStringList::split('\n', msg, true); msg = QString::null; for(QStringList::Iterator it = lines.begin(); it != lines.end(); it++) { QString line = *it; QString indent = splitLine( line ); // printf("Quoted Line = \"%s\" \"%s\"\n", line.ascii(), indent.ascii()); if ( line.isEmpty()) { if (!firstPart) part.append(QString::null); continue; }; if (firstPart) { oldIndent = indent; firstPart = false; } if (oldIndent != indent) { QString fromLine; // Search if the last non-blank line could be "From" line if (part.count() && (oldIndent.length() < indent.length())) { QStringList::Iterator it2 = part.fromLast(); while( (it2 != part.end()) && (*it2).isEmpty()) it2--; if ((it2 != part.end()) && ((*it2).endsWith(":"))) { fromLine = oldIndent + (*it2) + '\n'; part.remove(it2); } } if (flushPart( msg, part, oldIndent, maxLength)) { if (oldIndent.length() > indent.length()) msg += indent + '\n'; else msg += oldIndent + '\n'; } if (!fromLine.isEmpty()) { msg += fromLine; //printf("From = %s", fromLine.ascii()); } oldIndent = indent; } part.append(line); } flushPart( msg, part, oldIndent, maxLength); } //----------------------------------------------------------------------------- void KMMessage::parseTextStringFromDwPart( DwBodyPart * mainBody, DwBodyPart * firstBodyPart, QCString& parsedString, bool& isHTML ) const { // get a valid CryptPlugList CryptPlugWrapperList cryptPlugList; KConfig *config = KMKernel::config(); cryptPlugList.loadFromConfig( config ); isHTML = false; int mainType = type(); int mainSubType = subtype(); if( (DwMime::kTypeNull == mainType) || (DwMime::kTypeUnknown == mainType) ){ mainType = DwMime::kTypeText; mainSubType = DwMime::kSubtypePlain; } partNode rootNode( mainBody, mainType, mainSubType); if ( firstBodyPart ) { partNode* curNode = rootNode.setFirstChild( new partNode( firstBodyPart ) ); curNode->buildObjectTree(); } // initialy parse the complete message to decrypt any encrypted parts { ObjectTreeParser otp( 0, 0, true, false, true ); otp.parseObjectTree( &rootNode ); } partNode * curNode = rootNode.findType( DwMime::kTypeText, DwMime::kSubtypeUnknown, true, false ); kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart() - " << QString( curNode ? "text part found!\n" : "sorry, no text node!\n" ) << endl; if( curNode ) { isHTML = DwMime::kSubtypeHtml == curNode->type(); // now parse the TEXT message part we want to quote ObjectTreeParser otp( 0, 0, true, false, true ); otp.parseObjectTree( curNode ); parsedString = otp.resultString(); } kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart() - parsed string:\n\"" << QString( parsedString + "\"\n\n" ) << endl; } //----------------------------------------------------------------------------- QCString KMMessage::asQuotedString( const QString& aHeaderStr, const QString& aIndentStr, const QString& selection /* = QString::null */, bool aStripSignature /* = true */, bool allowDecryption /* = true */) const { QString result; QString headerStr; QRegExp reNL("\\n"); QString indentStr; int i; bool clearSigned = false; const QTextCodec *codec = mCodec; if (!codec) { QCString cset = charset(); if (!cset.isEmpty()) codec = KMMsgBase::codecForName(cset); if (!codec) codec = kernel->networkCodec(); } indentStr = formatString(aIndentStr); headerStr = formatString(aHeaderStr); QCString parsedString; bool isHTML = false; // Quote message. Do not quote mime message parts that are of other // type than "text". if (numBodyParts() == 0 || !selection.isEmpty() ) { if( !selection.isEmpty() ) { result = selection; } else { DwBodyPart * mainBody = 0; DwBodyPart * firstBodyPart = getFirstDwBodyPart(); if( !firstBodyPart ) { mainBody = new DwBodyPart(((KMMessage*)this)->asDwString(), 0); mainBody->Parse(); } parseTextStringFromDwPart( mainBody, firstBodyPart, parsedString, isHTML ); if( !parsedString.isEmpty() ) { Kpgp::Module* pgp = Kpgp::Module::getKpgp(); assert(pgp != 0); QPtrList pgpBlocks; QStrList nonPgpBlocks; if( allowDecryption && Kpgp::Module::prepareMessageForDecryption( parsedString, pgpBlocks, nonPgpBlocks ) ) { // Only decrypt/strip off the signature if there is only one OpenPGP // block in the message if( pgpBlocks.count() == 1 ) { Kpgp::Block* block = pgpBlocks.first(); if( ( block->type() == Kpgp::PgpMessageBlock ) || ( block->type() == Kpgp::ClearsignedBlock ) ) { if( block->type() == Kpgp::PgpMessageBlock ) // try to decrypt this OpenPGP block block->decrypt(); else { // strip off the signature block->verify(); clearSigned = true; } result = codec->toUnicode( nonPgpBlocks.first() ) + codec->toUnicode( block->text() ) + codec->toUnicode( nonPgpBlocks.last() ); } } } } if( result.isEmpty() ) result = codec->toUnicode( parsedString ); if( mDecodeHTML && isHTML ) { KHTMLPart htmlPart; htmlPart.setOnlyLocalReferences(true); htmlPart.setMetaRefreshEnabled(false); htmlPart.setPluginsEnabled(false); htmlPart.setJScriptEnabled(false); htmlPart.setJavaEnabled(false); htmlPart.begin(); htmlPart.write(result); htmlPart.end(); htmlPart.selectAll(); result = htmlPart.selectedText(); } } // Remove blank lines at the beginning for( i = 0; i < (int)result.length() && result[i] <= ' '; i++ ); while (i > 0 && result[i-1] == ' ') i--; result.remove(0,i); if (aStripSignature) stripSignature(result, clearSigned); result.replace(reNL, '\n' + indentStr); result = indentStr + result + '\n'; if (sSmartQuote) smartQuote(result, sWrapCol); } else { result = ""; DwBodyPart *dwPart = getFirstDwBodyPart(); if( dwPart ) parseTextStringFromDwPart( 0, dwPart, parsedString, isHTML ); if( !parsedString.isEmpty() ) { Kpgp::Module* pgp = Kpgp::Module::getKpgp(); assert(pgp != 0); QString part; QPtrList pgpBlocks; QStrList nonPgpBlocks; if( allowDecryption && Kpgp::Module::prepareMessageForDecryption( parsedString, pgpBlocks, nonPgpBlocks ) ) { // Only decrypt/strip off the signature if there is only one OpenPGP // block in this message part if( pgpBlocks.count() == 1 ) { Kpgp::Block* block = pgpBlocks.first(); if( ( block->type() == Kpgp::PgpMessageBlock ) || ( block->type() == Kpgp::ClearsignedBlock ) ) { if( block->type() == Kpgp::PgpMessageBlock ) // try to decrypt this OpenPGP block block->decrypt(); else { // strip off the signature block->verify(); clearSigned = true; } part = codec->toUnicode( nonPgpBlocks.first() ) + codec->toUnicode( block->text() ) + codec->toUnicode( nonPgpBlocks.last() ); } } } if( part.isEmpty() ) { // part = codec->toUnicode( msgPart.bodyDecoded() ); // debug ("part\n" + part ); inexplicably crashes -sanders part = codec->toUnicode( parsedString ); } if (aStripSignature) stripSignature(part, clearSigned); part.replace(reNL, '\n' + indentStr); part = indentStr + part + '\n'; if (sSmartQuote) smartQuote(part, sWrapCol); result += part; } } QCString c = QString(headerStr + result).utf8(); return c; } //----------------------------------------------------------------------------- // static QString KMMessage::stripOffPrefixes( const QString& str ) { return replacePrefixes( str, sReplySubjPrefixes + sForwardSubjPrefixes, true, QString::null ).stripWhiteSpace(); } //----------------------------------------------------------------------------- // static QString KMMessage::replacePrefixes( const QString& str, const QStringList& prefixRegExps, bool replace, const QString& newPrefix ) { bool recognized = false; // construct a big regexp that // 1. is anchored to the beginning of str (sans whitespace) // 2. matches at least one of the part regexps in prefixRegExps QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*") .arg( prefixRegExps.join(")|(?:") ); QRegExp rx( bigRegExp, false /*case insens.*/ ); if ( !rx.isValid() ) { kdWarning(5006) << "KMMessage::replacePrefixes(): bigRegExp = \"" << bigRegExp << "\"\n" << "prefix regexp is invalid!" << endl; // try good ole Re/Fwd: recognized = str.startsWith( newPrefix ); } else { // valid rx QString tmp = str; if ( rx.search( tmp ) == 0 ) { recognized = true; if ( replace ) return tmp.replace( 0, rx.matchedLength(), newPrefix + ' ' ); } } if ( !recognized ) return newPrefix + ' ' + str; else return str; } //----------------------------------------------------------------------------- QString KMMessage::cleanSubject() const { return cleanSubject( sReplySubjPrefixes + sForwardSubjPrefixes, true, QString::null ).stripWhiteSpace(); } //----------------------------------------------------------------------------- QString KMMessage::cleanSubject( const QStringList & prefixRegExps, bool replace, const QString & newPrefix ) const { return KMMessage::replacePrefixes( subject(), prefixRegExps, replace, newPrefix ); } //----------------------------------------------------------------------------- KMMessage* KMMessage::createReply( bool replyToAll /* = false */, bool replyToList /* = false */, QString selection /* = QString::null */, bool noQuote /* = false */, bool allowDecryption /* = true */, bool selectionIsBody /* = false */) { KMMessage* msg = new KMMessage; QString str, replyStr, mailingListStr, replyToStr, toStr; QCString refStr, headerName; msg->initFromMessage(this); KMMLInfo::name(this, headerName, mailingListStr); replyToStr = replyTo(); msg->setCharset("utf-8"); if (replyToList && parent() && parent()->isMailingList()) { // Reply to mailing-list posting address toStr = parent()->mailingListPostAddress(); } else if (replyToList && headerField("List-Post").find("mailto:", 0, false) != -1 ) { QString listPost = headerField("List-Post"); QRegExp rx( "]+)@([^>]+)>", false ); if ( rx.search( listPost, 0 ) != -1 ) // matched toStr = rx.cap(1) + '@' + rx.cap(2); } else if (replyToAll) { QStringList recipients; // add addresses from the Reply-To header to the list of recipients if (!replyToStr.isEmpty()) recipients += splitEmailAddrList(replyToStr); // add From address to the list of recipients if it's not already there if (!from().isEmpty()) if (recipients.grep(getEmailAddr(from()), false).isEmpty()) { recipients += from(); kdDebug(5006) << "Added " << from() << " to the list of recipients" << endl; } // add only new addresses from the To header to the list of recipients if (!to().isEmpty()) { QStringList list = splitEmailAddrList(to()); for (QStringList::Iterator it = list.begin(); it != list.end(); ++it ) { if (recipients.grep(getEmailAddr(*it), false).isEmpty()) { recipients += *it; kdDebug(5006) << "Added " << *it << " to the list of recipients" << endl; } } } // strip my own address from the list of recipients QString myAddr = getEmailAddr(msg->from()); for (QStringList::Iterator it = recipients.begin(); it != recipients.end(); ) { if ((*it).find(myAddr,0,false) != -1) { kdDebug(5006) << "Removing " << *it << " from the list of recipients" << endl; it = recipients.remove(it); } else ++it; } toStr = recipients.join(", "); // the same for the cc field if (!cc().isEmpty()) { recipients = splitEmailAddrList(cc()); // strip my own address for (QStringList::Iterator it = recipients.begin(); it != recipients.end(); ) { if ((*it).find(myAddr,0,false) != -1) { kdDebug(5006) << "Removing " << *it << " from the cc recipients" << endl; it = recipients.remove(it); } else ++it; } msg->setCc(recipients.join(", ")); } } else { if (!replyToStr.isEmpty()) toStr = replyToStr; else if (!from().isEmpty()) toStr = from(); } msg->setTo(toStr); refStr = getRefStr(); if (!refStr.isEmpty()) msg->setReferences(refStr); //In-Reply-To = original msg-id msg->setReplyToId(msgId()); if (replyToAll || replyToList || !mailingListStr.isEmpty() || (parent() && parent()->isMailingList())) replyStr = sReplyAllStr; else replyStr = sReplyStr; replyStr += "\n"; if (!noQuote) { if( selectionIsBody ){ QCString cStr = selection.latin1(); msg->setBody( cStr ); }else{ msg->setBody(asQuotedString(replyStr, sIndentPrefixStr, selection, sSmartQuote, allowDecryption)); } } msg->setSubject(cleanSubject(sReplySubjPrefixes, sReplaceSubjPrefix, "Re:")); // setStatus(KMMsgStatusReplied); msg->link(this, KMMsgStatusReplied); return msg; } //----------------------------------------------------------------------------- QCString KMMessage::getRefStr() const { QCString firstRef, lastRef, refStr, retRefStr; int i, j; refStr = headerField("References").stripWhiteSpace().latin1(); if (refStr.isEmpty()) return headerField("Message-Id").latin1(); i = refStr.find('<'); j = refStr.find('>'); firstRef = refStr.mid(i, j-i+1); if (!firstRef.isEmpty()) retRefStr = firstRef + ' '; i = refStr.findRev('<'); j = refStr.findRev('>'); lastRef = refStr.mid(i, j-i+1); if (!lastRef.isEmpty() && lastRef != firstRef) retRefStr += lastRef + ' '; retRefStr += headerField("Message-Id").latin1(); return retRefStr; } KMMessage* KMMessage::createRedirect(void) { KMMessage* msg = new KMMessage; KMMessagePart msgPart; int i; msg->initFromMessage(this); /// ### FIXME: The message should be redirected with the same Content-Type /// ### as the original message /// ### FIXME: ??Add some Resent-* headers?? (c.f. RFC2822 3.6.6) QString st = QString::fromUtf8(asQuotedString("", "", QString::null, false, false)); QCString encoding = autoDetectCharset(charset(), sPrefCharsets, st); if (encoding.isEmpty()) encoding = "utf-8"; QCString str = codecForName(encoding)->fromUnicode(st); msg->setCharset(encoding); msg->setBody(str); if (numBodyParts() > 0) { msgPart.setBody(str); msgPart.setTypeStr("text"); msgPart.setSubtypeStr("plain"); msgPart.setCharset(encoding); msg->addBodyPart(&msgPart); for (i = 0; i < numBodyParts(); i++) { bodyPart(i, &msgPart); if ((qstricmp(msgPart.contentDisposition(),"inline")!=0 && i > 0) || (qstricmp(msgPart.typeStr(),"text")!=0 && qstricmp(msgPart.typeStr(),"message")!=0)) { msg->addBodyPart(&msgPart); } } } //TODO: insert sender here msg->setHeaderField("X-KMail-Redirect-From", from()); msg->setSubject(subject()); msg->setFrom(from()); msg->cleanupHeader(); // setStatus(KMMsgStatusForwarded); msg->link(this, KMMsgStatusForwarded); return msg; } #if ALLOW_GUI KMMessage* KMMessage::createBounce( bool withUI ) #else KMMessage* KMMessage::createBounce( bool ) #endif { QString fromStr, bodyStr, senderStr; int atIdx, i; const char* fromFields[] = { "Errors-To", "Return-Path", "Resent-From", "Resent-Sender", "From", "Sender", 0 }; // Find email address of sender for (i=0; fromFields[i]; i++) { senderStr = headerField(fromFields[i]); if (!senderStr.isEmpty()) break; } if (senderStr.isEmpty()) { #if ALLOW_GUI if ( withUI ) KMessageBox::sorry(0 /*app-global modal*/, i18n("The message has no sender set"), i18n("Bounce Message")); #endif return 0; } QString receiver = headerField("Received"); int a = -1, b = -1; a = receiver.find("from"); if (a != -1) a = receiver.find("by", a); if (a != -1) a = receiver.find("for", a); if (a != -1) a = receiver.find('<', a); if (a != -1) b = receiver.find('>', a); if (a != -1 && b != -1) receiver = receiver.mid(a+1, b-a-1); else receiver = getEmailAddr(to()); #if ALLOW_GUI if ( withUI ) // No composer appears. So better ask before sending. if (KMessageBox::warningContinueCancel(0 /*app-global modal*/, i18n("Return the message to the sender as undeliverable?\n" "This will only work if the email address of the sender, " "%1, is valid.\n" "The failing address will be reported to be %2.") .arg(senderStr).arg(receiver), i18n("Bounce Message"), i18n("Continue")) == KMessageBox::Cancel) { return 0; } #endif KMMessage *msg = new KMMessage; msg->initFromMessage(this, FALSE); msg->setTo( senderStr ); msg->setDateToday(); msg->setSubject( "mail failed, returning to sender" ); fromStr = receiver; atIdx = fromStr.find('@'); msg->setFrom( fromStr.replace( 0, atIdx, "MAILER-DAEMON" ) ); msg->setReferences( id() ); bodyStr = "|------------------------- Message log follows: -------------------------|\n" "no valid recipients were found for this message\n" "|------------------------- Failed addresses follow: ---------------------|\n"; bodyStr += receiver; bodyStr += "\n|------------------------- Message text follows: ------------------------|\n"; bodyStr += asSendableString(); msg->setBody( bodyStr.latin1() ); msg->cleanupHeader(); return msg; } //----------------------------------------------------------------------------- QCString KMMessage::createForwardBody(void) { QString s; QCString str; if (sHeaderStrategy == HeaderStrategy::all()) { s = "\n\n---------- " + sForwardStr + " ----------\n\n"; s += headerAsString(); str = asQuotedString(s, "", QString::null, false, false); str += "\n-------------------------------------------------------\n"; } else { s = "\n\n---------- " + sForwardStr + " ----------\n\n"; s += "Subject: " + subject() + "\n"; s += "Date: " + KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized, date(), sReplyLanguage, false ) + "\n"; s += "From: " + from() + "\n"; s += "To: " + to() + "\n"; if (!cc().isEmpty()) s += "Cc: " + cc() + "\n"; s += "\n"; str = asQuotedString(s, "", QString::null, false, false); str += "\n-------------------------------------------------------\n"; } return str; } //----------------------------------------------------------------------------- KMMessage* KMMessage::createForward(void) { KMMessage* msg = new KMMessage; KMMessagePart msgPart; QString id; int i; msg->initFromMessage(this); QString st = QString::fromUtf8(createForwardBody()); QCString encoding = autoDetectCharset(charset(), sPrefCharsets, st); if (encoding.isEmpty()) encoding = "utf-8"; QCString str = codecForName(encoding)->fromUnicode(st); msg->setCharset(encoding); msg->setBody(str); if (numBodyParts() > 0) { msgPart.setTypeStr("text"); msgPart.setSubtypeStr("plain"); msgPart.setCharset(encoding); msgPart.setBody(str); msg->addBodyPart(&msgPart); for (i = 0; i < numBodyParts(); i++) { bodyPart(i, &msgPart); if (i > 0 || qstricmp(msgPart.typeStr(),"text") != 0) msg->addBodyPart(&msgPart); } } msg->setSubject(cleanSubject(sForwardSubjPrefixes, sReplaceForwSubjPrefix, "Fwd:")); msg->cleanupHeader(); // setStatus(KMMsgStatusForwarded); msg->link(this, KMMsgStatusForwarded); return msg; } static const struct { const char * dontAskAgainID; bool canDeny; const char * text; } mdnMessageBoxes[] = { { "mdnNormalAsk", true, I18N_NOOP("This message contains a request to send a disposition " "notification.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, { "mdnUnknownOption", false, I18N_NOOP("This message contains a request to send a disposition " "notification.\n" "It contains a processing instruction that is marked as " "\"required\", but which is unknown to KMail.\n" "You can either ignore the request or let KMail send a " "\"failed\" response.") }, { "mdnMultipleAddressesInReceiptTo", true, I18N_NOOP("This message contains a request to send a disposition " "notification,\n" "but it is requested to send the notification to more " "than one address.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, { "mdnReturnPathEmpty", true, I18N_NOOP("This message contains a request to send a disposition " "notification,\n" "but there is no return-path set.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, { "mdnReturnPathNotInReceiptTo", true, I18N_NOOP("This message contains a request to send a disposition " "notification,\n" "but the return-path address differs from the address " "the notification was requested to be sent to.\n" "You can either ignore the request or let KMail send a " "\"denied\" or normal response.") }, }; static const int numMdnMessageBoxes = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes; static int requestAdviceOnMDN( const char * what ) { for ( int i = 0 ; i < numMdnMessageBoxes ; ++i ) if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) ) if ( mdnMessageBoxes[i].canDeny ) { int answer = QMessageBox::information( 0, i18n("Message Disposition Notification Request"), i18n( mdnMessageBoxes[i].text ), i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") ); return answer ? answer + 1 : 0 ; // map to "mode" in createMDN } else { int answer = QMessageBox::information( 0, i18n("Message Disposition Notification Request"), i18n( mdnMessageBoxes[i].text ), i18n("&Ignore"), i18n("&Send") ); return answer ? answer + 2 : 0 ; // map to "mode" in createMDN } kdWarning(5006) << "didn't find data for message box \"" << what << "\"" << endl; return 0; } KMMessage* KMMessage::createMDN( MDN::ActionMode a, MDN::DispositionType d, bool allowGUI, QValueList m ) { // RFC 2298: At most one MDN may be issued on behalf of each // particular recipient by their user agent. That is, once an MDN // has been issued on behalf of a recipient, no further MDNs may be // issued on behalf of that recipient, even if another disposition // is performed on the message. //#define MDN_DEBUG 1 #ifndef MDN_DEBUG if ( mdnSentState() != KMMsgMDNStateUnknown && mdnSentState() != KMMsgMDNNone ) return 0; #else char st[2]; st[0] = (char)mdnSentState(); st[1] = 0; kdDebug(5006) << "mdnSentState() == '" << st << "'" << endl; #endif // RFC 2298: An MDN MUST NOT be generated in response to an MDN. if ( findDwBodyPart( DwMime::kTypeMessage, DwMime::kSubtypeDispositionNotification ) ) { setMDNSentState( KMMsgMDNIgnore ); return 0; } // extract where to send to: QString receiptTo = headerField("Disposition-Notification-To"); if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0; receiptTo.remove( '\n' ); MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user QString special; // fill in case of error, warning or failure KConfigGroup mdnConfig( KGlobal::config(), "MDN" ); // default: int mode = mdnConfig.readNumEntry( "default-policy", 0 ); if ( !mode || mode < 0 || mode > 3 ) { // early out for ignore: setMDNSentState( KMMsgMDNIgnore ); return 0; } // RFC 2298: An importance of "required" indicates that // interpretation of the parameter is necessary for proper // generation of an MDN in response to this request. If a UA does // not understand the meaning of the parameter, it MUST NOT generate // an MDN with any disposition type other than "failed" in response // to the request. QString notificationOptions = headerField("Disposition-Notification-Options"); if ( notificationOptions.contains( "required", false ) ) { // ### hacky; should parse... // There is a required option that we don't understand. We need to // ask the user what we should do: if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( "mdnUnknownOption" ); s = MDN::SentManually; special = i18n("Header \"Disposition-Notification-Options\" contained " "required, but unknown parameter"); d = MDN::Failed; m.clear(); // clear modifiers } // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no // MDN sent) ] if there is more than one distinct address in the // Disposition-Notification-To header. kdDebug(5006) << "splitEmailAddrList(receiptTo): " << splitEmailAddrList(receiptTo).join("\n") << endl; if ( splitEmailAddrList(receiptTo).count() > 1 ) { if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( "mdnMultipleAddressesInReceiptTo" ); s = MDN::SentManually; } // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in // the Disposition-Notification-To header differs from the address // in the Return-Path header. [...] Confirmation from the user // SHOULD be obtained (or no MDN sent) if there is no Return-Path // header in the message [...] QStrList returnPathList = headerAddrField("Return-Path"); QString returnPath = returnPathList.isEmpty() ? 0 : returnPathList.first() ; kdDebug(5006) << "clean return path: " << returnPath << endl; if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, false ) ) { if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( returnPath.isEmpty() ? "mdnReturnPathEmpty" : "mdnReturnPathNotInReceiptTo" ); s = MDN::SentManually; } if ( mode == 1 ) { // ask if ( !allowGUI ) return 0; // don't setMDNSentState here! mode = requestAdviceOnMDN( "mdnNormalAsk" ); s = MDN::SentManually; // asked user } switch ( mode ) { case 0: // ignore: setMDNSentState( KMMsgMDNIgnore ); return 0; default: case 1: kdFatal(5006) << "KMMessage::createMDN(): The \"ask\" mode should " << "never appear here!" << endl; break; case 2: // deny d = MDN::Denied; m.clear(); break; case 3: break; } // extract where to send from: QString finalRecipient = kernel->identityManager() ->identityForUoidOrDefault( identityUoid() ).fullEmailAddr(); // // Generate message: // KMMessage * receipt = new KMMessage(); receipt->initFromMessage( this ); receipt->removeHeaderField("Content-Type"); receipt->removeHeaderField("Content-Transfer-Encoding"); // Modify the ContentType directly (replaces setAutomaticFields(true)) DwHeaders & header = receipt->mMsg->Headers(); header.MimeVersion().FromString("1.0"); DwMediaType & contentType = receipt->dwContentType(); contentType.SetType( DwMime::kTypeMultipart ); contentType.SetSubtype( DwMime::kSubtypeReport ); contentType.CreateBoundary(0); receipt->mNeedsAssembly = true; receipt->setContentTypeParam( "report-type", "disposition-notification" ); QString description = replaceHeadersInString( MDN::descriptionFor( d, m ) ); // text/plain part: KMMessagePart firstMsgPart; firstMsgPart.setTypeStr( "text" ); firstMsgPart.setSubtypeStr( "plain" ); firstMsgPart.setBodyFromUnicode( description ); receipt->addBodyPart( &firstMsgPart ); // message/disposition-notification part: KMMessagePart secondMsgPart; secondMsgPart.setType( DwMime::kTypeMessage ); secondMsgPart.setSubtype( DwMime::kSubtypeDispositionNotification ); //secondMsgPart.setCharset( "us-ascii" ); //secondMsgPart.setCteStr( "7bit" ); secondMsgPart.setBodyEncoded( MDN::dispositionNotificationBodyContent( finalRecipient, rawHeaderField("Original-Recipient"), id(), /* Message-ID */ d, a, s, m, special ) ); receipt->addBodyPart( &secondMsgPart ); // message/rfc822 or text/rfc822-headers body part: int num = mdnConfig.readNumEntry( "quote-message", 0 ); if ( num < 0 || num > 2 ) num = 0; MDN::ReturnContent returnContent = static_cast( num ); KMMessagePart thirdMsgPart; switch ( returnContent ) { case MDN::All: thirdMsgPart.setTypeStr( "message" ); thirdMsgPart.setSubtypeStr( "rfc822" ); thirdMsgPart.setBody( asSendableString() ); receipt->addBodyPart( &thirdMsgPart ); break; case MDN::HeadersOnly: thirdMsgPart.setTypeStr( "text" ); thirdMsgPart.setSubtypeStr( "rfc822-headers" ); thirdMsgPart.setBody( headerAsSendableString() ); receipt->addBodyPart( &thirdMsgPart ); break; case MDN::Nothing: default: break; }; receipt->setTo( receiptTo ); receipt->setSubject( "Message Disposition Notification" ); receipt->setReplyToId( msgId() ); receipt->setReferences( getRefStr() ); receipt->cleanupHeader(); kdDebug(5006) << "final message:\n" + receipt->asString() << endl; // // Set "MDN sent" status: // KMMsgMDNSentState state = KMMsgMDNStateUnknown; switch ( d ) { case MDN::Displayed: state = KMMsgMDNDisplayed; break; case MDN::Deleted: state = KMMsgMDNDeleted; break; case MDN::Dispatched: state = KMMsgMDNDispatched; break; case MDN::Processed: state = KMMsgMDNProcessed; break; case MDN::Denied: state = KMMsgMDNDenied; break; case MDN::Failed: state = KMMsgMDNFailed; break; }; setMDNSentState( state ); return receipt; } QString KMMessage::replaceHeadersInString( const QString & s ) const { QString result = s; QRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false ); assert( rx.isValid() ); int idx = 0; while ( ( idx = rx.search( result, idx ) ) != -1 ) { QString replacement = headerField( rx.cap(1).latin1() ); result.replace( idx, rx.matchedLength(), replacement ); idx += replacement.length(); } return result; } QString KMMessage::forwardSubject() const { return cleanSubject( sForwardSubjPrefixes, sReplaceForwSubjPrefix, "Fwd:" ); } QString KMMessage::replySubject() const { return cleanSubject( sReplySubjPrefixes, sReplaceSubjPrefix, "Re:" ); } KMMessage* KMMessage::createDeliveryReceipt() const { QString str, receiptTo; KMMessage *receipt; receiptTo = headerField("Disposition-Notification-To"); if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0; receiptTo.replace(QRegExp("\\n"),""); receipt = new KMMessage; receipt->initFromMessage(this); receipt->setTo(receiptTo); receipt->setSubject(i18n("Receipt: ") + subject()); str = "Your message was successfully delivered."; str += "\n\n---------- Message header follows ----------\n"; str += headerAsString(); str += "--------------------------------------------\n"; // Conversion to latin1 is correct here as Mail headers should contain // ascii only receipt->setBody(str.latin1()); receipt->setAutomaticFields(); return receipt; } //----------------------------------------------------------------------------- void KMMessage::initHeader( uint id ) { const KMIdentity & ident = kernel->identityManager()->identityForUoidOrDefault( id ); if(ident.fullEmailAddr().isEmpty()) setFrom(""); else setFrom(ident.fullEmailAddr()); if(ident.replyToAddr().isEmpty()) setReplyTo(""); else setReplyTo(ident.replyToAddr()); if(ident.bcc().isEmpty()) setBcc(""); else setBcc(ident.bcc()); if (ident.organization().isEmpty()) removeHeaderField("Organization"); else setHeaderField("Organization", ident.organization()); if (ident.isDefault()) removeHeaderField("X-KMail-Identity"); else setHeaderField("X-KMail-Identity", QString().setNum( ident.uoid() )); if (ident.transport().isEmpty()) removeHeaderField("X-KMail-Transport"); else setHeaderField("X-KMail-Transport", ident.transport()); if (ident.fcc().isEmpty()) setFcc( QString::null ); else setFcc( ident.fcc() ); if (ident.drafts().isEmpty()) setDrafts( QString::null ); else setDrafts( ident.drafts() ); setTo(""); setSubject(""); setDateToday(); setHeaderField("User-Agent", "KMail/" KMAIL_VERSION ); // This will allow to change Content-Type: setHeaderField("Content-Type","text/plain"); } uint KMMessage::identityUoid() const { QString idString = headerField("X-KMail-Identity").stripWhiteSpace(); bool ok = false; int id = idString.toUInt( &ok ); if ( !ok || id == 0 ) id = kernel->identityManager()->identityForAddress( to() + cc() ).uoid(); if ( id == 0 && parent() ) id = parent()->identity(); return id; } //----------------------------------------------------------------------------- void KMMessage::initFromMessage(const KMMessage *msg, bool idHeaders) { uint id = msg->identityUoid(); if ( idHeaders ) initHeader(id); else setHeaderField("X-KMail-Identity", QString().setNum(id)); if (!msg->headerField("X-KMail-Transport").isEmpty()) setHeaderField("X-KMail-Transport", msg->headerField("X-KMail-Transport")); } //----------------------------------------------------------------------------- void KMMessage::cleanupHeader(void) { DwHeaders& header = mMsg->Headers(); DwField* field = header.FirstField(); DwField* nextField; if (mNeedsAssembly) mMsg->Assemble(); mNeedsAssembly = FALSE; while (field) { nextField = field->Next(); if (field->FieldBody()->AsString().empty()) { header.RemoveField(field); mNeedsAssembly = TRUE; } field = nextField; } } //----------------------------------------------------------------------------- void KMMessage::setAutomaticFields(bool aIsMulti) { DwHeaders& header = mMsg->Headers(); header.MimeVersion().FromString("1.0"); if (aIsMulti || numBodyParts() > 1) { // Set the type to 'Multipart' and the subtype to 'Mixed' DwMediaType& contentType = dwContentType(); contentType.SetType( DwMime::kTypeMultipart); contentType.SetSubtype(DwMime::kSubtypeMixed ); // Create a random printable string and set it as the boundary parameter contentType.CreateBoundary(0); } mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- QString KMMessage::dateStr(void) const { KConfigGroup general( KMKernel::config(), "General" ); DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); //kdDebug(5006)<<"#### Date = "<(general.readNumEntry( "dateFormat", KMime::DateFormatter::Fancy )), unixTime, general.readEntry( "customDateFormat" )); } //----------------------------------------------------------------------------- QCString KMMessage::dateShortStr(void) const { DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); QCString result = ctime(&unixTime); if (result[result.length()-1]=='\n') result.truncate(result.length()-1); return result; } //----------------------------------------------------------------------------- QString KMMessage::dateIsoStr(void) const { DwHeaders& header = mMsg->Headers(); time_t unixTime; if (!header.HasDate()) return ""; unixTime = header.Date().AsUnixTime(); char cstr[64]; strftime(cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&unixTime)); return QString(cstr); } //----------------------------------------------------------------------------- time_t KMMessage::date(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasDate()) return header.Date().AsUnixTime(); return (time_t)-1; } //----------------------------------------------------------------------------- void KMMessage::setDateToday(void) { struct timeval tval; gettimeofday(&tval, 0); setDate((time_t)tval.tv_sec); } //----------------------------------------------------------------------------- void KMMessage::setDate(time_t aDate) { mDate = aDate; mMsg->Headers().Date().FromCalendarTime(aDate); mMsg->Headers().Date().Assemble(); mNeedsAssembly = TRUE; mDirty = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setDate(const QCString& aStr) { DwHeaders& header = mMsg->Headers(); header.Date().FromString(aStr); header.Date().Parse(); mNeedsAssembly = TRUE; mDirty = TRUE; if (header.HasDate()) mDate = header.Date().AsUnixTime(); } //----------------------------------------------------------------------------- QString KMMessage::to(void) const { return headerField("To"); } //----------------------------------------------------------------------------- void KMMessage::setTo(const QString& aStr) { setHeaderField("To", aStr); } //----------------------------------------------------------------------------- QString KMMessage::toStrip(void) const { return stripEmailAddr(headerField("To")); } //----------------------------------------------------------------------------- QString KMMessage::replyTo(void) const { return headerField("Reply-To"); } //----------------------------------------------------------------------------- void KMMessage::setReplyTo(const QString& aStr) { setHeaderField("Reply-To", aStr); } //----------------------------------------------------------------------------- void KMMessage::setReplyTo(KMMessage* aMsg) { setHeaderField("Reply-To", aMsg->from()); } //----------------------------------------------------------------------------- QString KMMessage::cc(void) const { return headerField("Cc"); } //----------------------------------------------------------------------------- void KMMessage::setCc(const QString& aStr) { setHeaderField("Cc",aStr); } //----------------------------------------------------------------------------- QString KMMessage::bcc(void) const { return headerField("Bcc"); } //----------------------------------------------------------------------------- void KMMessage::setBcc(const QString& aStr) { setHeaderField("Bcc", aStr); } //----------------------------------------------------------------------------- QString KMMessage::fcc(void) const { return headerField( "X-KMail-Fcc" ); } //----------------------------------------------------------------------------- void KMMessage::setFcc(const QString& aStr) { setHeaderField( "X-KMail-Fcc", aStr ); } //----------------------------------------------------------------------------- void KMMessage::setDrafts(const QString& aStr) { mDrafts = aStr; kdDebug(5006) << "KMMessage::setDrafts " << aStr << endl; } //----------------------------------------------------------------------------- QString KMMessage::who(void) const { if (mParent) return headerField(mParent->whoField().utf8()); return headerField("From"); } //----------------------------------------------------------------------------- QString KMMessage::from(void) const { return headerField("From"); } //----------------------------------------------------------------------------- void KMMessage::setFrom(const QString& bStr) { QString aStr = bStr; if (aStr.isNull()) aStr = ""; setHeaderField("From", aStr); mDirty = TRUE; } //----------------------------------------------------------------------------- QString KMMessage::fromStrip(void) const { return stripEmailAddr(headerField("From")); } //----------------------------------------------------------------------------- QCString KMMessage::fromEmail(void) const { return getEmailAddr(headerField("From")); } //----------------------------------------------------------------------------- QString KMMessage::subject(void) const { return headerField("Subject"); } //----------------------------------------------------------------------------- void KMMessage::setSubject(const QString& aStr) { setHeaderField("Subject",aStr); mDirty = TRUE; } //----------------------------------------------------------------------------- QString KMMessage::xmark(void) const { return headerField("X-KMail-Mark"); } //----------------------------------------------------------------------------- void KMMessage::setXMark(const QString& aStr) { setHeaderField("X-KMail-Mark", aStr); mDirty = TRUE; } //----------------------------------------------------------------------------- QString KMMessage::replyToId(void) const { int leftAngle, rightAngle; QString replyTo, references; replyTo = headerField("In-Reply-To"); // search the end of the (first) message id in the In-Reply-To header rightAngle = replyTo.find( '>' ); if (rightAngle != -1) replyTo.truncate( rightAngle + 1 ); // now search the start of the message id leftAngle = replyTo.findRev( '<' ); if (leftAngle != -1) replyTo = replyTo.mid( leftAngle ); // if we have found a good message id we can return immediately // We ignore mangled In-Reply-To headers which are created by a // misconfigured Mutt. They look like this <"from foo"@bar.baz>, i.e. // they contain double quotes and spaces. We only check for '"'. if (!replyTo.isEmpty() && (replyTo[0] == '<') && ( -1 == replyTo.find( '"' ) ) ) return replyTo; references = headerField("References"); leftAngle = references.findRev( '<' ); if (leftAngle != -1) references = references.mid( leftAngle ); rightAngle = references.find( '>' ); if (rightAngle != -1) references.truncate( rightAngle + 1 ); // if we found a good message id in the References header return it if (!references.isEmpty() && references[0] == '<') return references; // else return the broken message id we found in the In-Reply-To header else return replyTo; } //----------------------------------------------------------------------------- QString KMMessage::replyToIdMD5(void) const { // QString result = KMMessagePart::encodeBase64( decodeRFC2047String(replyToId()) ); QString result = KMMessagePart::encodeBase64( replyToId() ); return result; } +//----------------------------------------------------------------------------- +QString KMMessage::references(void) const +{ + int leftAngle, rightAngle; + QString references = headerField("References"); + + // keep the last two entries for threading + leftAngle = references.findRev( '<' ); + leftAngle = references.findRev( '<', leftAngle-1 ); + if (leftAngle != -1) + references = references.mid( leftAngle ); + rightAngle = references.findRev( '>' ); + if (rightAngle != -1) + references.truncate( rightAngle + 1 ); + + if (!references.isEmpty() && references[0] == '<') + return references; + else + return ""; +} + +//----------------------------------------------------------------------------- +QString KMMessage::replyToAuxIdMD5(void) const +{ + int rightAngle; + QString result = references(); + // references contains two items, use the first one + // (the second to last reference) + rightAngle = result.find( '>' ); + if (rightAngle != -1) + result.truncate (rightAngle+1); + + return KMMessagePart::encodeBase64( result ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::strippedSubjectMD5(void) const +{ + QString result = stripOffPrefixes(subject()); + return KMMessagePart::encodeBase64( result ); +} + +//----------------------------------------------------------------------------- +bool KMMessage::subjectIsPrefixed(void) const +{ + return !(strippedSubjectMD5() == KMMessagePart::encodeBase64(subject())); + +} //----------------------------------------------------------------------------- void KMMessage::setReplyToId(const QString& aStr) { setHeaderField("In-Reply-To", aStr); mDirty = TRUE; } //----------------------------------------------------------------------------- QString KMMessage::msgId(void) const { int leftAngle, rightAngle; QString msgId = headerField("Message-Id"); // search the end of the message id rightAngle = msgId.find( '>' ); if (rightAngle != -1) msgId.truncate( rightAngle + 1 ); // now search the start of the message id leftAngle = msgId.findRev( '<' ); if (leftAngle != -1) msgId = msgId.mid( leftAngle ); return msgId; } //----------------------------------------------------------------------------- QString KMMessage::msgIdMD5(void) const { // QString result = KMMessagePart::encodeBase64( decodeRFC2047String(msgId()) ); QString result = KMMessagePart::encodeBase64( msgId() ); return result; } //----------------------------------------------------------------------------- void KMMessage::setMsgId(const QString& aStr) { setHeaderField("Message-Id", aStr); mDirty = TRUE; } //----------------------------------------------------------------------------- QStrList KMMessage::headerAddrField(const QCString& aName) const { QString header = headerField(aName); QStringList list = splitEmailAddrList(header); QStrList resultList; int i,j; for (QStringList::Iterator it = list.begin(); it != list.end(); it++) { i = (*it).find('<'); if (i >= 0) { j = (*it).find('>', i+1); if (j > i) (*it) = (*it).mid(i+1, j-i-1); } else // if it's "radej@kde.org (Sven Radej)" { i = (*it).find('('); if (i > 0) (*it).truncate(i); // "radej@kde.org " } (*it) = (*it).stripWhiteSpace(); if (!(*it).isEmpty()) resultList.append((*it).latin1()); } return resultList; } QCString KMMessage::rawHeaderField( const QCString & name ) const { if ( name.isEmpty() ) return QCString(); DwHeaders & header = mMsg->Headers(); DwField * field = header.FindField( name ); if ( !field ) return QCString(); return header.FieldBody( name.data() ).AsString().c_str(); } QString KMMessage::headerField(const QCString& aName) const { DwHeaders& header = mMsg->Headers(); DwField* field; QString result; if (aName.isEmpty() || !(field = header.FindField(aName))) result = ""; else result = decodeRFC2047String(header.FieldBody(aName.data()). AsString().c_str()); return result; } //----------------------------------------------------------------------------- void KMMessage::removeHeaderField(const QCString& aName) { DwHeaders& header = mMsg->Headers(); DwField* field; field = header.FindField(aName); if (!field) return; header.RemoveField(field); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setHeaderField(const QCString& aName, const QString& bValue) { if (aName.isEmpty()) return; DwHeaders& header = mMsg->Headers(); DwString str; DwField* field; QCString aValue = ""; if (!bValue.isEmpty()) { QCString encoding = autoDetectCharset(charset(), sPrefCharsets, bValue); if (encoding.isEmpty()) encoding = "utf-8"; aValue = encodeRFC2047String(bValue, encoding); } str = aName; if (str[str.length()-1] != ':') str += ": "; else str += ' '; str += aValue; if (str[str.length()-1] != '\n') str += '\n'; field = new DwField(str, mMsg); field->Parse(); header.AddOrReplaceField(field); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- QCString KMMessage::typeStr(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().AsString().c_str(); else return ""; } //----------------------------------------------------------------------------- int KMMessage::type(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().Type(); else return DwMime::kTypeNull; } //----------------------------------------------------------------------------- void KMMessage::setTypeStr(const QCString& aStr) { dwContentType().SetTypeStr(DwString(aStr)); dwContentType().Parse(); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setType(int aType) { dwContentType().SetType(aType); dwContentType().Assemble(); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- QCString KMMessage::subtypeStr(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().SubtypeStr().c_str(); else return ""; } //----------------------------------------------------------------------------- int KMMessage::subtype(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasContentType()) return header.ContentType().Subtype(); else return DwMime::kSubtypeNull; } //----------------------------------------------------------------------------- void KMMessage::setSubtypeStr(const QCString& aStr) { dwContentType().SetSubtypeStr(DwString(aStr)); dwContentType().Parse(); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setSubtype(int aSubtype) { dwContentType().SetSubtype(aSubtype); dwContentType().Assemble(); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setDwMediaTypeParam( DwMediaType &mType, const QCString& attr, const QCString& val ) { mType.Parse(); DwParameter *param = mType.FirstParameter(); while(param) { if (!qstricmp(param->Attribute().c_str(), attr)) break; else param = param->Next(); } if (!param){ param = new DwParameter; param->SetAttribute(DwString( attr )); mType.AddParameter( param ); } else mType.SetModified(); param->SetValue(DwString( val )); mType.Assemble(); } //----------------------------------------------------------------------------- void KMMessage::setContentTypeParam(const QCString& attr, const QCString& val) { if (mNeedsAssembly) mMsg->Assemble(); mNeedsAssembly = FALSE; setDwMediaTypeParam( dwContentType(), attr, val ); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- QCString KMMessage::contentTransferEncodingStr(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasContentTransferEncoding()) return header.ContentTransferEncoding().AsString().c_str(); else return ""; } //----------------------------------------------------------------------------- int KMMessage::contentTransferEncoding(void) const { DwHeaders& header = mMsg->Headers(); if (header.HasContentTransferEncoding()) return header.ContentTransferEncoding().AsEnum(); else return DwMime::kCteNull; } //----------------------------------------------------------------------------- void KMMessage::setContentTransferEncodingStr(const QCString& aStr) { mMsg->Headers().ContentTransferEncoding().FromString(aStr); mMsg->Headers().ContentTransferEncoding().Parse(); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setContentTransferEncoding(int aCte) { mMsg->Headers().ContentTransferEncoding().FromEnum(aCte); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- DwHeaders& KMMessage::headers() const { return mMsg->Headers(); } //----------------------------------------------------------------------------- void KMMessage::setNeedsAssembly(void) { mNeedsAssembly = true; } //----------------------------------------------------------------------------- QCString KMMessage::body(void) const { DwString body = mMsg->Body().AsString(); QCString str = body.c_str(); kdWarning( str.length() != body.length(), 5006 ) << "KMMessage::body(): body is binary but used as text!" << endl; return str; } //----------------------------------------------------------------------------- QByteArray KMMessage::bodyDecodedBinary(void) const { DwString dwstr; DwString dwsrc = mMsg->Body().AsString(); switch (cte()) { case DwMime::kCteBase64: DwDecodeBase64(dwsrc, dwstr); break; case DwMime::kCteQuotedPrintable: DwDecodeQuotedPrintable(dwsrc, dwstr); break; default: dwstr = dwsrc; break; } int len = dwstr.size(); QByteArray ba(len); memcpy(ba.data(),dwstr.data(),len); return ba; } //----------------------------------------------------------------------------- QCString KMMessage::bodyDecoded(void) const { DwString dwstr; DwString dwsrc = mMsg->Body().AsString(); switch (cte()) { case DwMime::kCteBase64: DwDecodeBase64(dwsrc, dwstr); break; case DwMime::kCteQuotedPrintable: DwDecodeQuotedPrintable(dwsrc, dwstr); break; default: dwstr = dwsrc; break; } unsigned int len = dwstr.size(); QCString result(len+1); memcpy(result.data(),dwstr.data(),len); result[len] = 0; kdWarning(result.length() != len, 5006) << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl; return result; } //----------------------------------------------------------------------------- QValueList KMMessage::determineAllowedCtes( const CharFreq& cf, bool allow8Bit, bool willBeSigned ) { QValueList allowedCtes; switch ( cf.type() ) { case CharFreq::SevenBitText: allowedCtes << DwMime::kCte7bit; case CharFreq::EightBitText: if ( allow8Bit ) allowedCtes << DwMime::kCte8bit; case CharFreq::SevenBitData: if ( cf.printableRatio() > 5.0/6.0 ) { // let n the length of data and p the number of printable chars. // Then base64 \approx 4n/3; qp \approx p + 3(n-p) // => qp < base64 iff p > 5n/6. allowedCtes << DwMime::kCteQp; allowedCtes << DwMime::kCteBase64; } else { allowedCtes << DwMime::kCteBase64; allowedCtes << DwMime::kCteQp; } break; case CharFreq::EightBitData: allowedCtes << DwMime::kCteBase64; break; case CharFreq::None: default: // just nothing (avoid compiler warning) ; } // In the following cases only QP and Base64 are allowed: // - the buffer will be OpenPGP/MIME signed and it contains trailing // whitespace (cf. RFC 3156) // - a line starts with "From " if ( ( willBeSigned && cf.hasTrailingWhitespace() ) || cf.hasLeadingFrom() ) { allowedCtes.remove( DwMime::kCte8bit ); allowedCtes.remove( DwMime::kCte7bit ); } return allowedCtes; } //----------------------------------------------------------------------------- void KMMessage::setBodyAndGuessCte( const QByteArray& aBuf, QValueList & allowedCte, bool allow8Bit, bool willBeSigned ) { CharFreq cf( aBuf ); // it's safe to pass null arrays allowedCte = KMMessage::determineAllowedCtes( cf, allow8Bit, willBeSigned ); #ifndef NDEBUG DwString dwCte; DwCteEnumToStr(allowedCte[0], dwCte); kdDebug(5006) << "CharFreq returned " << cf.type() << "/" << cf.printableRatio() << " and I chose " << dwCte.c_str() << endl; #endif setCte( allowedCte[0] ); // choose best fitting setBodyEncodedBinary( aBuf ); } //----------------------------------------------------------------------------- void KMMessage::setBodyAndGuessCte( const QCString& aBuf, QValueList & allowedCte, bool allow8Bit, bool willBeSigned ) { CharFreq cf( aBuf.data(), aBuf.length() ); // it's safe to pass null strings allowedCte = KMMessage::determineAllowedCtes( cf, allow8Bit, willBeSigned ); #ifndef NDEBUG DwString dwCte; DwCteEnumToStr(allowedCte[0], dwCte); kdDebug(5006) << "CharFreq returned " << cf.type() << "/" << cf.printableRatio() << " and I chose " << dwCte.c_str() << endl; #endif setCte( allowedCte[0] ); // choose best fitting setBodyEncoded( aBuf ); } //----------------------------------------------------------------------------- void KMMessage::setBodyEncoded(const QCString& aStr) { DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */); DwString dwResult; switch (cte()) { case DwMime::kCteBase64: DwEncodeBase64(dwSrc, dwResult); break; case DwMime::kCteQuotedPrintable: DwEncodeQuotedPrintable(dwSrc, dwResult); break; default: dwResult = dwSrc; break; } mMsg->Body().FromString(dwResult); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setBodyEncodedBinary(const QByteArray& aStr) { DwString dwSrc(aStr.data(), aStr.size()); DwString dwResult; switch (cte()) { case DwMime::kCteBase64: DwEncodeBase64(dwSrc, dwResult); break; case DwMime::kCteQuotedPrintable: DwEncodeQuotedPrintable(dwSrc, dwResult); break; default: dwResult = dwSrc; break; } mMsg->Body().FromString(dwResult); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::setBody(const QCString& aStr) { mMsg->Body().FromString(aStr.data()); mNeedsAssembly = TRUE; } void KMMessage::setMultiPartBody( const QCString & aStr ) { setBody( aStr ); mMsg->Body().Parse(); mNeedsAssembly = true; } // Patched by Daniel Moisset // modified numbodyparts, bodypart to take nested body parts as // a linear sequence. // third revision, Sep 26 2000 // this is support structure for traversing tree without recursion //----------------------------------------------------------------------------- int KMMessage::numBodyParts(void) const { int count = 0; DwBodyPart* part = getFirstDwBodyPart(); QPtrList< DwBodyPart > parts; QString mp = "multipart"; while (part) { //dive into multipart messages while ( part && part->hasHeaders() && part->Headers().HasContentType() && (mp == part->Headers().ContentType().TypeStr().c_str()) ) { parts.append( part ); part = part->Body().FirstBodyPart(); } // this is where currPart->msgPart contains a leaf message part count++; // go up in the tree until reaching a node with next // (or the last top-level node) while (part && !(part->Next()) && !(parts.isEmpty())) { part = parts.getLast(); parts.removeLast(); }; if (part) part = part->Next(); } return count; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::getFirstDwBodyPart() const { return mMsg->Body().FirstBodyPart(); } //----------------------------------------------------------------------------- int KMMessage::partNumber( DwBodyPart * aDwBodyPart ) const { DwBodyPart *curpart; QPtrList< DwBodyPart > parts; int curIdx = 0; int idx = 0; // Get the DwBodyPart for this index curpart = getFirstDwBodyPart(); while (curpart && !idx) { //dive into multipart messages while( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) { parts.append( curpart ); curpart = curpart->Body().FirstBodyPart(); } // this is where currPart->msgPart contains a leaf message part if (curpart == aDwBodyPart) idx = curIdx; curIdx++; // go up in the tree until reaching a node with next // (or the last top-level node) while (curpart && !(curpart->Next()) && !(parts.isEmpty())) { curpart = parts.getLast(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } return idx; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const { DwBodyPart *part, *curpart; QPtrList< DwBodyPart > parts; int curIdx = 0; // Get the DwBodyPart for this index curpart = getFirstDwBodyPart(); part = 0; while (curpart && !part) { //dive into multipart messages while( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) { parts.append( curpart ); curpart = curpart->Body().FirstBodyPart(); } // this is where currPart->msgPart contains a leaf message part if (curIdx==aIdx) part = curpart; curIdx++; // go up in the tree until reaching a node with next // (or the last top-level node) while (curpart && !(curpart->Next()) && !(parts.isEmpty())) { curpart = parts.getLast(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } return part; } //----------------------------------------------------------------------------- DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const { DwBodyPart *part, *curpart; QPtrList< DwBodyPart > parts; // Get the DwBodyPart for this index curpart = getFirstDwBodyPart(); part = 0; while (curpart && !part) { //dive into multipart messages while(curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) { parts.append( curpart ); curpart = curpart->Body().FirstBodyPart(); } // this is where curPart->msgPart contains a leaf message part // pending(khz): Find out WHY this look does not travel down *into* an // embedded "Message/RfF822" message containing a "Multipart/Mixed" if (curpart && curpart->hasHeaders() ) { kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str() << " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl; } if (curpart && curpart->hasHeaders() && curpart->Headers().ContentType().Type() == type && curpart->Headers().ContentType().Subtype() == subtype) { part = curpart; } else { // go up in the tree until reaching a node with next // (or the last top-level node) while (curpart && !(curpart->Next()) && !(parts.isEmpty())) { curpart = parts.getLast(); parts.removeLast(); } ; if (curpart) curpart = curpart->Next(); } } return part; } //----------------------------------------------------------------------------- void KMMessage::bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart, bool withBody) { if( aPart ) { if( aDwBodyPart && aDwBodyPart->hasHeaders() ) { // This must not be an empty string, because we'll get a // spurious empty Subject: line in some of the parts. aPart->setName(" "); DwHeaders& headers = aDwBodyPart->Headers(); // Content-type QCString additionalCTypeParams; if (headers.HasContentType()) { DwMediaType& ct = headers.ContentType(); aPart->setOriginalContentTypeStr( ct.AsString().c_str() ); aPart->setTypeStr(ct.TypeStr().c_str()); aPart->setSubtypeStr(ct.SubtypeStr().c_str()); DwParameter *param = ct.FirstParameter(); while(param) { if (!qstricmp(param->Attribute().c_str(), "charset")) aPart->setCharset(QCString(param->Value().c_str()).lower()); else if (param->Attribute().c_str()=="name*") aPart->setName(KMMsgBase::decodeRFC2231String( param->Value().c_str())); else { additionalCTypeParams += ';'; additionalCTypeParams += param->AsString().c_str(); } param=param->Next(); } } else { aPart->setTypeStr("text"); // Set to defaults aPart->setSubtypeStr("plain"); } aPart->setAdditionalCTypeParamStr( additionalCTypeParams ); // Modification by Markus if (aPart->name().isEmpty() || aPart->name() == " ") { if (!headers.ContentType().Name().empty()) { aPart->setName(KMMsgBase::decodeRFC2047String(headers. ContentType().Name().c_str()) ); } else if (!headers.Subject().AsString().empty()) { aPart->setName( KMMsgBase::decodeRFC2047String(headers. Subject().AsString().c_str()) ); } } // Content-transfer-encoding if (headers.HasContentTransferEncoding()) aPart->setCteStr(headers.ContentTransferEncoding().AsString().c_str()); else aPart->setCteStr("7bit"); // Content-description if (headers.HasContentDescription()) aPart->setContentDescription(headers.ContentDescription().AsString().c_str()); else aPart->setContentDescription(""); // Content-disposition if (headers.HasContentDisposition()) aPart->setContentDisposition(headers.ContentDisposition().AsString().c_str()); else aPart->setContentDisposition(""); // Body if (withBody) aPart->setBody( aDwBodyPart->Body().AsString().c_str() ); else aPart->setBody( "" ); } // If no valid body part was not given, // set all MultipartBodyPart attributes to empty values. else { aPart->setTypeStr(""); aPart->setSubtypeStr(""); aPart->setCteStr(""); // This must not be an empty string, because we'll get a // spurious empty Subject: line in some of the parts. aPart->setName(" "); aPart->setContentDescription(""); aPart->setContentDisposition(""); aPart->setBody(""); } } } //----------------------------------------------------------------------------- void KMMessage::bodyPart(int aIdx, KMMessagePart* aPart) const { if( aPart ) { // If the DwBodyPart was found get the header fields and body DwBodyPart *part = dwBodyPart( aIdx ); if( part ) { KMMessage::bodyPart(part, aPart); if( aPart->name().isEmpty() ) aPart->setName( i18n("Attachment: ") + QString( "%1" ).arg( aIdx ) ); } } } //----------------------------------------------------------------------------- void KMMessage::deleteBodyParts(void) { mMsg->Body().DeleteBodyParts(); } //----------------------------------------------------------------------------- DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) { DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0); if( aPart ) { QCString charset = aPart->charset(); QCString type = aPart->typeStr(); QCString subtype = aPart->subtypeStr(); QCString cte = aPart->cteStr(); QCString contDesc = aPart->contentDescriptionEncoded(); QCString contDisp = aPart->contentDisposition(); QCString encoding = autoDetectCharset(charset, sPrefCharsets, aPart->name()); if (encoding.isEmpty()) encoding = "utf-8"; QCString name = KMMsgBase::encodeRFC2231String(aPart->name(), encoding); bool RFC2231encoded = aPart->name() != QString(name); QCString paramAttr = aPart->parameterAttribute(); DwHeaders& headers = part->Headers(); DwMediaType& ct = headers.ContentType(); if (!type.isEmpty() && !subtype.isEmpty()) { ct.SetTypeStr(type.data()); ct.SetSubtypeStr(subtype.data()); if (!charset.isEmpty()){ DwParameter *param; param=new DwParameter; param->SetAttribute("charset"); param->SetValue(charset.data()); ct.AddParameter(param); } } QCString additionalParam = aPart->additionalCTypeParamStr(); if( !additionalParam.isEmpty() ) { QCString parAV; DwString parA, parV; int iL, i1, i2, iM; iL = additionalParam.length(); i1 = 0; i2 = additionalParam.find(';', i1, false); while ( i1 < iL ) { if( -1 == i2 ) i2 = iL; if( i1+1 < i2 ) { parAV = additionalParam.mid( i1, (i2-i1) ); iM = parAV.find('='); if( -1 < iM ) { parA = parAV.left( iM ); parV = parAV.right( parAV.length() - iM - 1 ); if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) ) { parV.erase( 0, 1); parV.erase( parV.length()-1 ); } } else { parA = parAV; parV = ""; } DwParameter *param; param = new DwParameter; param->SetAttribute( parA ); param->SetValue( parV ); ct.AddParameter( param ); } i1 = i2+1; i2 = additionalParam.find(';', i1, false); } } if (RFC2231encoded) { DwParameter *nameParam; nameParam = new DwParameter; nameParam->SetAttribute("name*"); nameParam->SetValue(name.data(),true); ct.AddParameter(nameParam); } else { if(!name.isEmpty()) ct.SetName(name.data()); } if (!paramAttr.isEmpty()) { QCString encoding = autoDetectCharset(charset, sPrefCharsets, aPart->parameterValue()); if (encoding.isEmpty()) encoding = "utf-8"; QCString paramValue; paramValue = KMMsgBase::encodeRFC2231String(aPart->parameterValue(), encoding); DwParameter *param = new DwParameter; if (aPart->parameterValue() != QString(paramValue)) { param->SetAttribute((paramAttr + '*').data()); param->SetValue(paramValue.data(),true); } else { param->SetAttribute(paramAttr.data()); param->SetValue(paramValue.data()); } ct.AddParameter(param); } if (!cte.isEmpty()) headers.Cte().FromString(cte); if (!contDesc.isEmpty()) headers.ContentDescription().FromString(contDesc); if (!contDisp.isEmpty()) headers.ContentDisposition().FromString(contDisp); if (!aPart->body().isNull()) part->Body().FromString(aPart->body()); else part->Body().FromString(""); } return part; } //----------------------------------------------------------------------------- void KMMessage::addDwBodyPart(DwBodyPart * aDwPart) { mMsg->Body().AddBodyPart( aDwPart ); mNeedsAssembly = TRUE; } //----------------------------------------------------------------------------- void KMMessage::addBodyPart(const KMMessagePart* aPart) { DwBodyPart* part = createDWBodyPart( aPart ); addDwBodyPart( part ); } //----------------------------------------------------------------------------- void KMMessage::viewSource(const QString& aCaption, const QTextCodec *codec, bool fixedfont) { QString str = (codec) ? codec->toUnicode(asString()) : kernel->networkCodec()->toUnicode(asString()); #if ALLOW_GUI KMTextBrowser *browser = new KMTextBrowser(); // deletes itself upon close if (!aCaption.isEmpty()) browser->setCaption(aCaption); browser->setText(str); if (fixedfont) browser->setFont(KGlobalSettings::fixedFont()); // Well, there is no widget to be seen here, so we have to use QCursor::pos() // Update: (GS) I'm not going to make this code behave according to Xinerama // configuration because this is quite the hack. if (QApplication::desktop()->isVirtualDesktop()) { int scnum = QApplication::desktop()->screenNumber(QCursor::pos()); browser->resize(QApplication::desktop()->screenGeometry(scnum).width()/2, 2*QApplication::desktop()->screenGeometry(scnum).height()/3); } else { browser->resize(QApplication::desktop()->geometry().width()/2, 2*QApplication::desktop()->geometry().height()/3); } browser->show(); #else //not ALLOW_GUI kdDebug(5006) << "Message source: " << (aCaption.isEmpty() ? "" : (const char*)aCaption) << "\n" << str << "\n--- end of message ---" << endl; #endif } //----------------------------------------------------------------------------- QString KMMessage::generateMessageId( const QString& addr ) { QDateTime datetime = QDateTime::currentDateTime(); QString msgIdStr; msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" ); QString msgIdSuffix; KConfigGroup general( KMKernel::config(), "General" ); if( general.readBoolEntry( "useCustomMessageIdSuffix", false ) ) msgIdSuffix = general.readEntry( "myMessageIdSuffix", "" ); if( !msgIdSuffix.isEmpty() ) msgIdStr += '@' + msgIdSuffix; else msgIdStr += '.' + addr; msgIdStr += '>'; return msgIdStr; } //----------------------------------------------------------------------------- QCString KMMessage::html2source( const QCString & src ) { QCString result( 1 + 6*src.length() ); // maximal possible length QCString::ConstIterator s = src.begin(); QCString::Iterator d = result.begin(); while ( *s ) { switch ( *s ) { case '<': { *d++ = '&'; *d++ = 'l'; *d++ = 't'; *d++ = ';'; ++s; } break; case '\r': { ++s; } break; case '\n': { *d++ = '<'; *d++ = 'b'; *d++ = 'r'; *d++ = ' '; *d++ = '/'; *d++ = '>'; ++s; } break; case '>': { *d++ = '&'; *d++ = 'g'; *d++ = 't'; *d++ = ';'; ++s; } break; case '&': { *d++ = '&'; *d++ = 'a'; *d++ = 'm'; *d++ = 'p'; *d++ = ';'; ++s; } break; case '\"': { *d++ = '&'; *d++ = 'q'; *d++ = 'u'; *d++ = 'o'; *d++ = 't'; *d++ = ';'; ++s; } break; default: *d++ = *s++; } } result.truncate( d - result.begin() ); // adds trailing NUL return result; } //----------------------------------------------------------------------------- QCString KMMessage::lf2crlf( const QCString & src ) { QCString result( 1 + 2*src.length() ); // maximal possible length QCString::ConstIterator s = src.begin(); QCString::Iterator d = result.begin(); // we use cPrev to make sure we insert '\r' only there where it is missing char cPrev = '?'; while ( *s ) { if ( ('\n' == *s) && ('\r' != cPrev) ) *d++ = '\r'; cPrev = *s; *d++ = *s++; } result.truncate( d - result.begin() ); // adds trailing NUL return result; } //----------------------------------------------------------------------------- QString KMMessage::encodeMailtoUrl( const QString& str ) { QString result; result = QString::fromLatin1( KMMsgBase::encodeRFC2047String( str, "utf-8" ) ); result = KURL::encode_string( result ); return result; } //----------------------------------------------------------------------------- QString KMMessage::decodeMailtoUrl( const QString& url ) { QString result; result = KURL::decode_string( url ); result = KMMsgBase::decodeRFC2047String( result.latin1() ); return result; } //----------------------------------------------------------------------------- QString KMMessage::stripEmailAddr(const QString& aStr) { QStringList list = splitEmailAddrList(aStr); QString result, totalResult, partA, partB; int i, j, len; for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) { char endCh = '>'; i = -1; //if format is something like "---- John King" if ( (*it)[0] == '"' ) { i = 0; endCh = '"'; } if (i<0) { i = (*it).find('<'); endCh = '>'; } if (i<0) { i = (*it).find('('); endCh = ')'; } if (i<0) result = *it; else { partA = (*it).left(i).stripWhiteSpace(); j = (*it).find(endCh,i+1); if (j<0) result = *it; else { partB = (*it).mid(i+1, j-i-1).stripWhiteSpace(); if (partA.find('@') >= 0 && !partB.isEmpty()) result = partB; else if (!partA.isEmpty()) result = partA; else if (endCh == '"') result = partB; else result = (*it); len = result.length(); if (result[0]=='"' && result[len-1]=='"') result = result.mid(1, result.length()-2); else if (result[0]=='<' && result[len-1]=='>') result = result.mid(1, result.length()-2); else if (result[0]=='(' && result[len-1]==')') result = result.mid(1, result.length()-2); } } if (!totalResult.isEmpty()) totalResult += ", "; totalResult += result; } return totalResult; } //----------------------------------------------------------------------------- QCString KMMessage::getEmailAddr(const QString& aStr) { int a, i, j, len, found = 0; QChar c; // Find the '@' in the email address: a = aStr.find('@'); if (a<0) return aStr.latin1(); // Loop backwards until we find '<', '(', ' ', or beginning of string. for (i = a - 1; i >= 0; i--) { c = aStr[i]; if (c == '<' || c == '(' || c == ' ') found = 1; if (found) break; } // Reset found for next loop. found = 0; // Loop forwards until we find '>', ')', ' ', or end of string. for (j = a + 1; j < (int)aStr.length(); j++) { c = aStr[j]; if (c == '>' || c == ')' || c == ' ') found = 1; if (found) break; } // Calculate the length and return the result. len = j - (i + 1); return aStr.mid(i+1,len).latin1(); } //----------------------------------------------------------------------------- QString KMMessage::quoteHtmlChars( const QString& str, bool removeLineBreaks ) { QString result; int resultLength = 0; result.setLength( 6*str.length() ); // maximal possible length QChar ch; for( unsigned int i = 0; i < str.length(); ++i ) { ch = str[i]; if( '<' == ch ) { result[resultLength++] = '&'; result[resultLength++] = 'l'; result[resultLength++] = 't'; result[resultLength++] = ';'; } else if ( '>' == ch ) { result[resultLength++] = '&'; result[resultLength++] = 'g'; result[resultLength++] = 't'; result[resultLength++] = ';'; } else if( '&' == ch ) { result[resultLength++] = '&'; result[resultLength++] = 'a'; result[resultLength++] = 'm'; result[resultLength++] = 'p'; result[resultLength++] = ';'; } else if( '"' == ch ) { result[resultLength++] = '&'; result[resultLength++] = 'q'; result[resultLength++] = 'u'; result[resultLength++] = 'o'; result[resultLength++] = 't'; result[resultLength++] = ';'; } else if( '\n' == ch ) { if( !removeLineBreaks ) { result[resultLength++] = '<'; result[resultLength++] = 'b'; result[resultLength++] = 'r'; result[resultLength++] = ' '; result[resultLength++] = '/'; result[resultLength++] = '>'; } } else if( '\r' == ch ) { // ignore CR } else { result[resultLength++] = ch; } } result.truncate( resultLength ); // get rid of the undefined junk return result; } //----------------------------------------------------------------------------- QString KMMessage::emailAddrAsAnchor(const QString& aEmail, bool stripped) { if( aEmail.isEmpty() ) return aEmail; QStringList addressList = KMMessage::splitEmailAddrList( aEmail ); QString result; for( QStringList::ConstIterator it = addressList.begin(); ( it != addressList.end() ); ++it ) { if( !(*it).isEmpty() ) { QString address = *it; result += ""; if( stripped ) address = KMMessage::stripEmailAddr( address ); result += KMMessage::quoteHtmlChars( address, true ); result += ", "; } } // cut of the trailing ", " result.truncate( result.length() - 2 ); kdDebug(5006) << "KMMessage::emailAddrAsAnchor('" << aEmail << "') returns:\n-->" << result << "<--" << endl; return result; } //----------------------------------------------------------------------------- QStringList KMMessage::splitEmailAddrList(const QString& aStr) { // Features: // - always ignores quoted characters // - ignores everything (including parentheses and commas) // inside quoted strings // - supports nested comments // - ignores everything (including double quotes and commas) // inside comments QStringList list; if (aStr.isEmpty()) return list; QString addr; uint addrstart = 0; int commentlevel = 0; bool insidequote = false; for (uint index=0; index 0) commentlevel--; else { kdDebug(5006) << "Error in address splitting: Unmatched ')'" << endl; return list; } } break; case '\\' : // quoted character index++; // ignore the quoted character break; case ',' : if (!insidequote && (commentlevel == 0)) { addr = aStr.mid(addrstart, index-addrstart); if (!addr.isEmpty()) list += addr.simplifyWhiteSpace(); addrstart = index+1; } break; } } // append the last address to the list if (!insidequote && (commentlevel == 0)) { addr = aStr.mid(addrstart, aStr.length()-addrstart); if (!addr.isEmpty()) list += addr.simplifyWhiteSpace(); } else kdDebug(5006) << "Error in address splitting: " << "Unexpected end of address list" << endl; return list; } //----------------------------------------------------------------------------- void KMMessage::setTransferInProgress(bool value) { value ? ++mTransferInProgress : --mTransferInProgress; assert(mTransferInProgress >= 0 && mTransferInProgress <= 1); } //----------------------------------------------------------------------------- void KMMessage::readConfig(void) { KConfig *config=KMKernel::config(); KConfigGroupSaver saver(config, "General"); config->setGroup("General"); int languageNr = config->readNumEntry("reply-current-language",0); { // area for config group "KMMessage #n" KConfigGroupSaver saver(config, QString("KMMessage #%1").arg(languageNr)); sReplyLanguage = config->readEntry("language",KGlobal::locale()->language()); sReplyStr = config->readEntry("phrase-reply", i18n("On %D, you wrote:")); sReplyAllStr = config->readEntry("phrase-reply-all", i18n("On %D, %F wrote:")); sForwardStr = config->readEntry("phrase-forward", i18n("Forwarded Message")); sIndentPrefixStr = config->readEntry("indent-prefix",">%_"); } { // area for config group "Composer" KConfigGroupSaver saver(config, "Composer"); sReplySubjPrefixes = config->readListEntry("reply-prefixes", ','); if (sReplySubjPrefixes.count() == 0) sReplySubjPrefixes << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:"; sReplaceSubjPrefix = config->readBoolEntry("replace-reply-prefix", true); sForwardSubjPrefixes = config->readListEntry("forward-prefixes", ','); if (sForwardSubjPrefixes.count() == 0) sForwardSubjPrefixes << "Fwd:" << "FW:"; sReplaceForwSubjPrefix = config->readBoolEntry("replace-forward-prefix", true); sSmartQuote = config->readBoolEntry("smart-quote", true); sWrapCol = config->readNumEntry("break-at", 78); if ((sWrapCol == 0) || (sWrapCol > 78)) sWrapCol = 78; if (sWrapCol < 30) sWrapCol = 30; sPrefCharsets = config->readListEntry("pref-charsets"); } { // area for config group "Reader" KConfigGroupSaver saver(config, "Reader"); sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) ); } } QCString KMMessage::defaultCharset() { QCString retval; if (!sPrefCharsets.isEmpty()) retval = sPrefCharsets[0].latin1(); if (retval.isEmpty() || (retval == "locale")) retval = QCString(kernel->networkCodec()->mimeName()).lower(); if (retval == "jisx0208.1983-0") retval = "iso-2022-jp"; else if (retval == "ksc5601.1987-0") retval = "euc-kr"; return retval; } const QStringList &KMMessage::preferredCharsets() { return sPrefCharsets; } //----------------------------------------------------------------------------- QCString KMMessage::charset(void) const { DwMediaType &mType=mMsg->Headers().ContentType(); mType.Parse(); DwParameter *param=mType.FirstParameter(); while(param){ if (!qstricmp(param->Attribute().c_str(), "charset")) return param->Value().c_str(); else param=param->Next(); } return ""; // us-ascii, but we don't have to specify it } //----------------------------------------------------------------------------- void KMMessage::setCharset(const QCString& bStr) { QCString aStr = bStr.lower(); if (aStr.isNull()) aStr = ""; DwMediaType &mType = dwContentType(); mType.Parse(); DwParameter *param=mType.FirstParameter(); while(param) // FIXME use the mimelib functions here for comparison. if (!qstricmp(param->Attribute().c_str(), "charset")) break; else param=param->Next(); if (!param){ param=new DwParameter; param->SetAttribute("charset"); mType.AddParameter(param); } else mType.SetModified(); param->SetValue(DwString(aStr)); mType.Assemble(); } //----------------------------------------------------------------------------- void KMMessage::setStatus(const KMMsgStatus aStatus, int idx) { if (mStatus == aStatus) return; KMMsgBase::setStatus(aStatus, idx); mStatus = aStatus; mDirty = TRUE; } void KMMessage::setEncryptionState(const KMMsgEncryptionState s, int idx) { if( mEncryptionState == s ) return; mEncryptionState = s; mDirty = true; KMMsgBase::setEncryptionState(s, idx); } void KMMessage::setSignatureState(KMMsgSignatureState s, int idx) { if( mSignatureState == s ) return; mSignatureState = s; mDirty = true; KMMsgBase::setSignatureState(s, idx); } void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx ) { if ( mMDNSentState == status ) return; mMDNSentState = status; mDirty = true; KMMsgBase::setMDNSentState( status, idx ); } //----------------------------------------------------------------------------- void KMMessage::link(const KMMessage *aMsg, KMMsgStatus aStatus) { Q_ASSERT(aStatus == KMMsgStatusReplied || aStatus == KMMsgStatusForwarded); QString message = headerField("X-KMail-Link-Message"); if (!message.isEmpty()) message += ','; QString type = headerField("X-KMail-Link-Type"); if (!type.isEmpty()) type += ','; message += QString::number(aMsg->getMsgSerNum()); if (aStatus == KMMsgStatusReplied) type += "reply"; else if (aStatus == KMMsgStatusForwarded) type += "forward"; setHeaderField("X-KMail-Link-Message", message); setHeaderField("X-KMail-Link-Type", type); } //----------------------------------------------------------------------------- void KMMessage::getLink(int n, ulong *retMsgSerNum, KMMsgStatus *retStatus) const { *retMsgSerNum = 0; *retStatus = KMMsgStatusUnknown; QString message = headerField("X-KMail-Link-Message"); QString type = headerField("X-KMail-Link-Type"); message = message.section(',', n, n); type = type.section(',', n, n); if (!message.isEmpty() && !type.isEmpty()) { *retMsgSerNum = message.toULong(); if (type == "reply") *retStatus = KMMsgStatusReplied; else if (type == "forward") *retStatus = KMMsgStatusForwarded; } } diff --git a/kmail/kmmessage.h b/kmail/kmmessage.h index 62ba8f5892..c8a608be87 100644 --- a/kmail/kmmessage.h +++ b/kmail/kmmessage.h @@ -1,741 +1,770 @@ /* kmmessage.h: Mime Message Class * */ #ifndef kmmessage_h #define kmmessage_h // for large file support #include #include #include #include "kmmsgbase.h" #include template class QValueList; class QStringList; class QString; class QTextCodec; class QStrList; class KMFolder; class KMFolderIndex; class DwMessage; class KMMessagePart; class KMMsgInfo; class KMHeaders; namespace KMime { class CharFreq; }; namespace KMail { class HeaderStrategy; }; class DwBodyPart; class DwMediaType; class DwHeaders; #define KMMessageInherited KMMsgBase class KMMessage: public KMMsgBase { friend class KMForwardCommand; // needed for MIME Digest forward public: /** Straight forward initialization. */ KMMessage(KMFolderIndex* parent=0); /** Constructor from a DwMessage. */ KMMessage(DwMessage*); /** Copy constructor. Does *not* automatically load the message. */ KMMessage(KMMsgInfo& msgInfo); /** Copy constructor. */ KMMessage( const KMMessage& other ); //KMMessage( const KMMessage& other, // bool preserveArrivalTime=false ); // note: By setting preserveArrivalTime true you get // a message containing the arrival time of the // old one - this is usefull if this new message // is to replace the old one in the same folder // note2: temporarily uncommented this again (khz) /** Assignment operator. */ const KMMessage& operator=( const KMMessage& other ) { //const KMMessage& operator=( const KMMessage& other, // bool preserveArrivalTime=false ) { // note: By setting preserveArrivalTime true you get // a message containing the arrival time of the // old one - this is usefull if this new message // is to replace the old one in the same folder // note2: temporarily uncommented this again (khz) if( &other == this ) return *this; assign( other ); return *this; } /** Destructor. */ virtual ~KMMessage(); /** Returns TRUE if object is a real message (not KMMsgInfo or KMMsgBase) */ virtual bool isMessage(void) const; /** @return whether the priority: or x-priority headers indicate that this message should be considered urgent **/ bool isUrgent() const; /** Specifies an unencrypted copy of this message to be stored in a separate member variable to allow saving messages in unencrypted form that were sent in encrypted form. NOTE: Target of this pointer becomes property of KMMessage, and will be deleted in the d'tor. */ void setUnencryptedMsg( KMMessage* unencrypted ); /** Returns TRUE is the massage contains an unencrypted copy of itself. */ virtual bool hasUnencryptedMsg() const { return 0 != mUnencryptedMsg; } /** Returns an unencrypted copy of this message or 0 if none exists. */ virtual KMMessage* unencryptedMsg() const { return mUnencryptedMsg; } /** Returns an unencrypted copy of this message or 0 if none exists. \note This functions removed the internal unencrypted message pointer from the message: the process calling takeUnencryptedMsg() must delete the returned pointer when no longer needed. */ virtual KMMessage* takeUnencryptedMsg() { KMMessage* ret = mUnencryptedMsg; mUnencryptedMsg = 0; return ret; } /** Mark the message as deleted */ void del(void) { setStatus(KMMsgStatusDeleted); } /** Undelete the message. Same as touch */ void undel(void) { setStatus(KMMsgStatusOld); } /** Touch the message - mark it as read */ void touch(void) { setStatus(KMMsgStatusOld); } /** Create a new message that is a reply to this message, filling all required header fields with the proper values. The returned message is not stored in any folder. Marks this message as replied. */ virtual KMMessage* createReply(bool replyToAll=FALSE, bool replyToList=FALSE, QString selection=QString::null, bool noQuote=FALSE, bool allowDecryption=TRUE, bool selectionIsBody=FALSE); /** Create a new message that is a redirect to this message, filling all required header fields with the proper values. The returned message is not stored in any folder. Marks this message as replied. Redirects differ from forwards so they are forwarded to some other user, mail is not changed and the reply-to field is set to the email adress of the original sender */ virtual KMMessage* createRedirect(); /** Create a new message that is a "failed delivery" reply to this message, filling all required header fields with the proper values. The returned message is not stored in any folder. If @p withUI is true, asks the user if he really wants that. */ virtual KMMessage* createBounce( bool withUI ); /** Create the forwarded body for the message. */ virtual QCString createForwardBody(void); /** Create a new message that is a forward of this message, filling all required header fields with the proper values. The returned message is not stored in any folder. Marks this message as forwarded. */ virtual KMMessage* createForward(void); /** Create a new message that is a delivery receipt of this message, filling required header fileds with the proper values. The returned message is not stored in any folder. */ KMMessage* createDeliveryReceipt() const; /** Create a new message that is a MDN for this message, filling all required felds with proper values. Th ereturned message is not stored in any folder. @param a Use AutomaticAction for filtering and ManualAction for user-induced events. @param d See docs for @ref KMime::MDN::DispositionType @param m See docs for @ref KMime::MDN::DispositionModifier @param allowGUI Set to true if this method is allowed to ask the user questions @return The notification message or 0, if none should be sent. **/ KMMessage* createMDN( KMime::MDN::ActionMode a, KMime::MDN::DispositionType d, bool allowGUI=false, QValueList m=QValueList() ); /** Parse the string and create this message from it. */ virtual void fromDwString(const DwString& str, bool setStatus=FALSE); virtual void fromString(const QCString& str, bool setStatus=FALSE); /** Return the entire message contents in the DwString. This function is *fast* even for large message since it does *not* involve a string copy. */ virtual const DwString& asDwString() const; virtual const DwMessage *asDwMessage(void); /** Return the entire message contents as a string. This function is slow for large message since it involves a string copy. If you need the string representation only for a short time (i.e. without the chance of calling any function in the underlying mimelib, then you should use the @ref asByteArray, which is more efficient or use the @ref asDwString function. @see asByteArray @see asDwString */ virtual QCString asString() const; /** * Return the message contents with the headers that should not be * sent stripped off. */ QCString asSendableString() const; /** * Return the message header with the headers that should not be * sent stripped off. */ QCString headerAsSendableString() const; /** * Remove all private header fields: *Status: and X-KMail-* **/ void removePrivateHeaderFields(); /** Return reference to Content-Type header for direct manipulation. */ DwMediaType& dwContentType(void); /** Return header as string. */ virtual QString headerAsString(void) const; /** Returns a decoded body part string to be further processed by function asQuotedString(). THIS FUNCTION WILL BE REPLACED ONCE KMime IS FULLY INTEGRATED (khz, June 05 2002)*/ virtual void parseTextStringFromDwPart( DwBodyPart * mainBody, DwBodyPart * firstBodyPart, QCString& parsedString, bool& isHTML ) const; /** Returns message body with quoting header and indented by the given indentation string. This is suitable for including the message in another message of for replies, forwards. The header string is a template where the following fields are replaced with the corresponding values:
 	%D: date of this message
 	%S: subject of this message
 	%F: sender (from) of this message
 	%%: a single percent sign
     
No attachments are handled if includeAttach is false. The signature is stripped if aStripSignature is true and smart quoting is turned on. Signed or encrypted texts get converted to plain text when allowDecryption is true. */ virtual QCString asQuotedString(const QString& headerStr, const QString& indentStr, const QString& selection=QString::null, bool aStripSignature=true, bool allowDecryption=true) const; /** Initialize header fields. Should be called on new messages if they are not set manually. E.g. before composing. Calling of setAutomaticFields(), see below, is still required. */ virtual void initHeader(uint identity=0); /** Initialize headers fields according to the identity and the transport header of the given original message */ virtual void initFromMessage(const KMMessage *msg, bool idHeaders = TRUE); /** @return the UOID of the identity for this message. Searches the "x-kmail-identity" header and if that fails, searches with @ref IdentityManager::identityForAddress() and if that fails queries the @ref #parent() folde for a default. **/ uint identityUoid() const; /** Removes empty fields from the header, e.g. an empty Cc: or Bcc: field. */ virtual void cleanupHeader(void); /** Set fields that are either automatically set (Message-id) or that do not change from one message to another (MIME-Version). Call this method before sending *after* all changes to the message are done because this method does things different if there are attachments / multiple body parts. */ virtual void setAutomaticFields(bool isMultipart=FALSE); /** Get or set the 'Date' header field */ virtual QString dateStr(void) const; virtual QCString dateShortStr(void) const; virtual QString dateIsoStr(void) const; virtual time_t date(void) const; virtual void setDate(const QCString& str); virtual void setDate(time_t aUnixTime); /** Set the 'Date' header field to the current date. */ virtual void setDateToday(void); /** Get or set the 'To' header field */ virtual QString to(void) const; virtual void setTo(const QString& aStr); virtual QString toStrip(void) const; /** Get or set the 'ReplyTo' header field */ virtual QString replyTo(void) const; virtual void setReplyTo(const QString& aStr); virtual void setReplyTo(KMMessage*); /** Get or set the 'Cc' header field */ virtual QString cc(void) const; virtual void setCc(const QString& aStr); /** Get or set the 'Bcc' header field */ virtual QString bcc(void) const; virtual void setBcc(const QString& aStr); /** Get or set the 'Fcc' header field */ virtual QString fcc(void) const; virtual void setFcc(const QString& aStr); /** Get or set the 'Drafts' folder */ virtual QString drafts(void) const { return mDrafts; } virtual void setDrafts(const QString& aStr); /** Get or set the 'From' header field */ virtual QString from(void) const; virtual void setFrom(const QString& aStr); virtual QString fromStrip(void) const; virtual QCString fromEmail(void) const; /** Get or set the 'Who' header field. The actual field that is returned depends on the contents of the owning folders whoField(). Usually this is 'From', but it can also contain 'To'. */ virtual QString who(void) const; /** Get or set the 'Subject' header field */ virtual QString subject(void) const; virtual void setSubject(const QString& aStr); + /** Calculate strippedSubject */ + virtual void initStrippedSubjectMD5() {}; + /** Check for prefixes @p prefixRegExps in @p str. If none is found, @p newPrefix + ' ' is prepended to @p str and the resulting string is returned. If @p replace is true, any sequence of whitespace-delimited prefixes at the beginning of @p str is replaced by @p newPrefix. **/ static QString replacePrefixes( const QString& str, const QStringList& prefixRegExps, bool replace, const QString& newPrefix ); /** Returns @p str with all "forward" and "reply" prefixes stripped off. **/ static QString stripOffPrefixes( const QString& str ); /** Check for prefixes @p prefixRegExps in @ref #subject(). If none is found, @p newPrefix + ' ' is prepended to the subject and the resulting string is returned. If @p replace is true, any sequence of whitespace-delimited prefixes at the beginning of - @ref #subject() is replaced by @p newPrefix. + @ref #subject() is replaced by @p newPrefix **/ QString cleanSubject(const QStringList& prefixRegExps, bool replace, const QString& newPrefix) const; /** Return this mails subject, with all "forward" and "reply" prefixes removed */ QString cleanSubject() const; /** Return this mails subject, formatted for "forward" mails */ QString forwardSubject() const; /** Return this mails subject, formatted for "reply" mails */ QString replySubject() const; /** Get or set the 'X-Mark' header field */ virtual QString xmark(void) const; virtual void setXMark(const QString& aStr); /** Get or set the 'In-Reply-To' header field */ virtual QString replyToId(void) const; virtual void setReplyToId(const QString& aStr); virtual QString replyToIdMD5(void) const; + /** Get the second to last id from the References header + field. If outgoing messages are not kept in the same + folder as incoming ones, this will be a good place to + thread the message beneath. + bob <- second to last reference points to this + |_kmailuser <- not in our folder, but Outbox + |_bob <- In-Reply-To points to our mail above + + Thread like this: + bob + |_bob + + using replyToAuxIdMD5 + */ + virtual QString replyToAuxIdMD5(void) const; + + /** + Get a hash of the subject with all prefixes such as Re: removed. + Used for threading. + */ + virtual QString strippedSubjectMD5(void) const; + + /** Is the subject prefixed by Re: or similar? */ + virtual bool subjectIsPrefixed(void) const; + /** Get or set the 'Message-Id' header field */ virtual QString msgId(void) const; virtual void setMsgId(const QString& aStr); virtual QString msgIdMD5(void) const; - /** Set the references for this message */ + /** Get or set the references for this message */ + virtual QString references(void) const; virtual void setReferences(const QCString& aStr); /** Returns the message ID, useful for followups */ virtual QCString id(void) const; /** Returns the message serial number. */ virtual unsigned long getMsgSerNum() const; /** Sets the message serial number. If defaulted to zero, the serial number will be assigned using the dictionary. */ virtual void setMsgSerNum(unsigned long newMsgSerNum = 0); /** Get or set header field with given name */ virtual QString headerField(const QCString& name) const; virtual void setHeaderField(const QCString& name, const QString& value); /** Get a raw header field */ QCString rawHeaderField( const QCString & name ) const; /** Returns header address list as string list. Warning: returns a temporary object ! Valid for the following fields: To, Bcc, Cc, ReplyTo, ResentBcc, ResentCc, ResentReplyTo, ResentTo */ virtual QStrList headerAddrField(const QCString& name) const; /** Remove header field with given name */ virtual void removeHeaderField(const QCString& name); /** Get or set the 'Content-Type' header field The member functions that involve enumerated types (ints) will work only for well-known types or subtypes. */ virtual QCString typeStr(void) const; virtual int type(void) const; virtual void setTypeStr(const QCString& aStr); virtual void setType(int aType); /** Subtype */ virtual QCString subtypeStr(void) const; virtual int subtype(void) const; virtual void setSubtypeStr(const QCString& aStr); virtual void setSubtype(int aSubtype); /** add or change a parameter of a DwMediaType field */ static void setDwMediaTypeParam( DwMediaType &mType, const QCString& attr, const QCString& val ); /** add or change a parameter of the Content-Type field */ virtual void setContentTypeParam(const QCString& attr, const QCString& val); /** get the DwHeaders (make sure to call setNeedsAssembly() function after directly modyfying internal data like the headers) */ virtual DwHeaders& headers() const; /** tell the message that internal data were changed (must be called after directly modifying message structures e.g. when like changing header information by accessing the header via headers() function) */ void setNeedsAssembly(void); /** Get or set the 'Content-Transfer-Encoding' header field The member functions that involve enumerated types (ints) will work only for well-known encodings. */ virtual QCString contentTransferEncodingStr(void) const; virtual int contentTransferEncoding(void) const; virtual void setContentTransferEncodingStr(const QCString& aStr); virtual void setContentTransferEncoding(int aCte); /** Cte is short for ContentTransferEncoding. These functions are an alternative to the ones with longer names. */ QCString cteStr(void) const { return contentTransferEncodingStr(); } int cte(void) const { return contentTransferEncoding(); } void setCteStr(const QCString& aStr) { setContentTransferEncodingStr(aStr); } void setCte(int aCte) { setContentTransferEncoding(aCte); } /** Get the message body. Does not decode the body. */ virtual QCString body(void) const; /** Set the message body. Does not encode the body. */ virtual void setBody(const QCString& aStr); /** Hack to enable structured body parts to be set as flat text... */ void setMultiPartBody( const QCString & aStr ); /** Set the message body, encoding it according to the current content transfer encoding. The first method for null terminated strings, the second for binary data */ virtual void setBodyEncoded(const QCString& aStr); virtual void setBodyEncodedBinary(const QByteArray& aStr); /** Returns a list of content-transfer-encodings that can be used with the given result of the character frequency analysis of a message or message part under the given restrictions. */ static QValueList determineAllowedCtes( const KMime::CharFreq& cf, bool allow8Bit, bool willBeSigned ); /** Sets body, encoded in the best fitting content-transfer-encoding, which is determined by character frequency count. @param aBuf input buffer @param allowedCte return: list of allowed cte's @param allow8Bit whether "8bit" is allowed as cte. @param willBeSigned whether "7bit"/"8bit" is allowed as cte according to RFC 3156 */ virtual void setBodyAndGuessCte( const QByteArray& aBuf, QValueList& allowedCte, bool allow8Bit = false, bool willBeSigned = false ); virtual void setBodyAndGuessCte( const QCString& aBuf, QValueList& allowedCte, bool allow8Bit = false, bool willBeSigned = false ); /** Returns a decoded version of the body from the current content transfer encoding. The first method returns a null terminated string, the second method is meant for binary data, not null is appended */ virtual QCString bodyDecoded(void) const; virtual QByteArray bodyDecodedBinary(void) const; /** Number of body parts the message has. This is one for plain messages without any attachment. */ virtual int numBodyParts(void) const; /** Return the first DwBodyPart matching a given Content-Type or zero, if no found. */ virtual DwBodyPart * findDwBodyPart( int type, int subtype ) const; /** Get the DwBodyPart at position in aIdx. Indexing starts at 0. If there is no body part at that index, return value will be zero. */ virtual DwBodyPart * dwBodyPart( int aIdx ) const; /** Get the number of the given DwBodyPart. If no body part is given, return value will be -1. */ int partNumber( DwBodyPart * aDwBodyPart ) const; /** Get the 1st DwBodyPart. If there is no body part, return value will be zero. */ DwBodyPart * getFirstDwBodyPart() const; /** Fill the KMMessagePart structure for a given DwBodyPart. Iff withBody is false the body of the KMMessagePart will be left empty and only the headers of the part will be filled in*/ static void bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart, bool withBody = true ); /** Get the body part at position in aIdx. Indexing starts at 0. If there is no body part at that index, aPart will have its attributes set to empty values. */ virtual void bodyPart(int aIdx, KMMessagePart* aPart) const; /** Compose a DwBodyPart (needed for adding a part to the message). */ virtual DwBodyPart* createDWBodyPart(const KMMessagePart* aPart); /** Append a DwBodyPart to the message. */ virtual void addDwBodyPart(DwBodyPart * aDwPart); /** Append a body part to the message. */ virtual void addBodyPart(const KMMessagePart* aPart); /** Delete all body parts. */ virtual void deleteBodyParts(void); /** Open a window containing the complete, unparsed, message. */ virtual void viewSource(const QString& windowCaption, const QTextCodec *codec, bool fixedfont); /** Set "Status" and "X-Status" fields of the message from the * internal message status. */ virtual void setStatusFields(void); /** Generates the Message-Id. It uses either the Message-Id suffix * defined by the user or the given email address as suffix. The address * must be given as addr-spec as defined in RFC 2822. */ static QString generateMessageId( const QString& addr ); /** Convert '<' into "<" resp. '>' into ">" in order to * prevent their interpretation by KHTML. * Does *not* use the Qt replace function but runs a very fast C code * the same way as lf2crlf() does. */ static QCString html2source( const QCString & src ); /** Convert LF line-ends to CRLF */ static QCString lf2crlf( const QCString & src ); /** Encodes an email address as mailto URL */ static QString encodeMailtoUrl( const QString& str ); /** Decodes a mailto URL */ static QString decodeMailtoUrl( const QString& url ); /** Strip email address from string. Examples: * "Stefan Taferner " returns "Stefan Taferner" * "joe@nowhere.com" returns "joe@nowhere.com". Note that this only * returns the first name, e.g. "Peter Test , Harald Tester " * returns "Peter Test" */ static QString stripEmailAddr(const QString& emailAddr); /** Return email address from string. Examples: * "Stefan Taferner " returns "taferner@kde.org" * "joe@nowhere.com" returns "joe@nowhere.com". Note that this only * returns the first address. */ static QCString getEmailAddr(const QString& emailAddr); /** Quotes the following characters which have a special meaning in HTML: * '<' '>' '&' '"'. Additionally '\n' is converted to "
" if * @p removeLineBreaks is false. If @p removeLineBreaks is true, then * '\n' is removed. Last but not least '\r' is removed. */ static QString quoteHtmlChars( const QString& str, bool removeLineBreaks = false ); /** Converts the email address(es) to (a) nice HTML mailto: anchor(s). * If stripped is TRUE then the visible part of the anchor contains * only the name part and not the given emailAddr. */ static QString emailAddrAsAnchor(const QString& emailAddr, bool stripped=TRUE); /** Split a comma separated list of email addresses. */ static QStringList splitEmailAddrList(const QString&); /** Get the default message charset.*/ static QCString defaultCharset(void); /** Get a list of preferred message charsets.*/ static const QStringList &preferredCharsets(void); /** Replaces every occurrence of "${foo}" in @p s with @ref headerField("foo") */ QString replaceHeadersInString( const QString & s ) const; /** Get the message charset.*/ virtual QCString charset(void) const; /** Set the message charset. */ virtual void setCharset(const QCString& aStr); /** Get the charset the user selected for the message to display */ virtual const QTextCodec* codec(void) const { return mCodec; } /** Set the charset the user selected for the message to display */ virtual void setCodec(const QTextCodec* aCodec) { mCodec = aCodec; } /** Allow decoding of HTML for quoting */ void setDecodeHTML(bool aDecodeHTML) { mDecodeHTML = aDecodeHTML; } /** Return if the message is complete and not only the header of a message * in an IMAP folder */ virtual bool isComplete() { return mIsComplete; } /** Set if the message is a complete message */ virtual void setComplete(bool value) { mIsComplete = value; } /** Return, if the message should not be deleted */ virtual bool transferInProgress() { return mTransferInProgress; } /** Set that the message shall not be deleted because it is still required */ virtual void setTransferInProgress(bool value); /** Reads config settings from group "KMMessage" and sets all internal * variables (e.g. indent-prefix, etc.) */ static void readConfig(void); /** Creates reference string for reply to messages. * reference = original first reference + original last reference + original msg-id */ QCString getRefStr() const; /** Get/set offset in mail folder. */ virtual off_t folderOffset(void) const { return mFolderOffset; } void setFolderOffset(off_t offs) { if(mFolderOffset != offs) { mFolderOffset=offs; setDirty(TRUE); } } /** Get/set filename in mail folder. */ virtual QString fileName(void) const { return mFileName; } void setFileName(const QString& file) { if(mFileName != file) { mFileName=file; setDirty(TRUE); } } /** Get/set size of message in the folder including the whole header in bytes. Can be 0, if the message is not in a folder. The setting of mMsgSize = mMsgLength = sz is needed for popFilter*/ virtual size_t msgSize(void) const { return mMsgSize; } void setMsgSize(size_t sz) { if(mMsgSize != sz) { mMsgSize = sz; setDirty(TRUE); } } /** Unlike the above funtion this works also, if the message is not in a folder */ virtual size_t msgLength(void) const { return (mMsgLength) ? mMsgLength : mMsgSize; } void setMsgLength(size_t sz) { mMsgLength = sz; } /** Status of the message. */ virtual KMMsgStatus status(void) const { return mStatus; } /** Set status and mark dirty. */ virtual void setStatus(const KMMsgStatus status, int idx = -1); virtual void setStatus(const char* s1, const char* s2=0) { KMMsgBase::setStatus(s1, s2); } /** Set encryption status of the message. */ virtual void setEncryptionState(const KMMsgEncryptionState, int idx = -1); /** Set signature status of the message. */ virtual void setSignatureState(const KMMsgSignatureState, int idx = -1); virtual void setMDNSentState( KMMsgMDNSentState status, int idx=-1 ); /** Encryption status of the message. */ virtual KMMsgEncryptionState encryptionState() const { return mEncryptionState; } /** Signature status of the message. */ virtual KMMsgSignatureState signatureState() const { return mSignatureState; } virtual KMMsgMDNSentState mdnSentState() const { return mMDNSentState; } /** Links this message to @p aMsg, setting link type to @p aStatus. */ void link(const KMMessage *aMsg, KMMsgStatus aStatus); /** Returns the information for the Nth link into @p retMsg * and @p retStatus. */ void getLink(int n, ulong *retMsgSerNum, KMMsgStatus *retStatus) const; /** Convert wildcards into normal string */ QString formatString(const QString&) const; protected: void assign( const KMMessage& other ); QString mDrafts; protected: mutable DwMessage* mMsg; mutable bool mNeedsAssembly; bool mIsComplete, mDecodeHTML; int mTransferInProgress; static const KMail::HeaderStrategy * sHeaderStrategy; static QString sForwardStr; const QTextCodec* mCodec; QString mFileName; off_t mFolderOffset; size_t mMsgSize, mMsgLength; time_t mDate; KMMsgStatus mStatus; unsigned long mMsgSerNum; KMMsgEncryptionState mEncryptionState; KMMsgSignatureState mSignatureState; KMMsgMDNSentState mMDNSentState; KMMessage* mUnencryptedMsg; }; typedef KMMessage* KMMessagePtr; #endif /*kmmessage_h*/ diff --git a/kmail/kmmsgbase.cpp b/kmail/kmmsgbase.cpp index 94d0fa0fa7..2c0e6a9d08 100644 --- a/kmail/kmmsgbase.cpp +++ b/kmail/kmmsgbase.cpp @@ -1,1068 +1,1075 @@ // kmmsgbase.cpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_BYTESWAP_H #include #endif // We define functions as kmail_swap_NN so that we don't get compile errors // on platforms where bswap_NN happens to be a function instead of a define. /* Swap bytes in 16 bit value. */ #ifdef bswap_16 #define kmail_swap_16(x) bswap_16(x) #else #define kmail_swap_16(x) \ ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)) #endif /* Swap bytes in 32 bit value. */ #ifdef bswap_32 #define kmail_swap_32(x) bswap_32(x) #else #define kmail_swap_32(x) \ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) #endif /* Swap bytes in 64 bit value. */ #ifdef bswap_64 #define kmail_swap_64(x) bswap_64(x) #else #define kmail_swap_64(x) \ ((((x) & 0xff00000000000000ull) >> 56) \ | (((x) & 0x00ff000000000000ull) >> 40) \ | (((x) & 0x0000ff0000000000ull) >> 24) \ | (((x) & 0x000000ff00000000ull) >> 8) \ | (((x) & 0x00000000ff000000ull) << 8) \ | (((x) & 0x0000000000ff0000ull) << 24) \ | (((x) & 0x000000000000ff00ull) << 40) \ | (((x) & 0x00000000000000ffull) << 56)) #endif static KMMsgStatus sStatusList[] = { KMMsgStatusDeleted, KMMsgStatusNew, KMMsgStatusUnread, KMMsgStatusOld, KMMsgStatusRead, KMMsgStatusReplied, KMMsgStatusSent, KMMsgStatusQueued, KMMsgStatusFlag, KMMsgStatusUnknown /* "Unknown" must be at the *end* of the list */ }; static const int NUM_STATUSLIST = sizeof sStatusList / sizeof *sStatusList; //----------------------------------------------------------------------------- KMMsgBase::KMMsgBase(KMFolderIndex* aParent) { mParent = aParent; mDirty = FALSE; mIndexOffset = 0; mIndexLength = 0; } //----------------------------------------------------------------------------- KMMsgBase::~KMMsgBase() { } //----------------------------------------------------------------------------- void KMMsgBase::assign(const KMMsgBase* other) { mParent = other->mParent; mDirty = other->mDirty; mIndexOffset = other->mIndexOffset; mIndexLength = other->mIndexLength; } //----------------------------------------------------------------------------- KMMsgBase& KMMsgBase::operator=(const KMMsgBase& other) { assign(&other); return *this; } //---------------------------------------------------------------------------- KMMsgBase::KMMsgBase( const KMMsgBase& other ) { assign( &other ); } //----------------------------------------------------------------------------- bool KMMsgBase::isMessage(void) const { return FALSE; } //----------------------------------------------------------------------------- void KMMsgBase::setStatus(const KMMsgStatus aStatus, int idx) { if ((idx < 0) && (mParent)) idx = mParent->find( this ); if (mParent) mParent->msgStatusChanged( status(), aStatus, idx ); mDirty = TRUE; if (mParent) mParent->headerOfMsgChanged(this, idx); } //----------------------------------------------------------------------------- void KMMsgBase::setStatus(const char* aStatusStr, const char* aXStatusStr) { setStatus(KMMsgStatusUnknown); // first try to find status from "X-Status" field if given if (aXStatusStr) { for (int i=0; iheaderOfMsgChanged(this, idx); } void KMMsgBase::setEncryptionStateChar( QChar status, int idx ) { kdDebug(5006) << "***setEncryptionState2( " << (status.isNull() ? '?' : status.latin1()) << " )" << endl; if( status.latin1() == (char)KMMsgEncryptionStateUnknown ) setEncryptionState( KMMsgEncryptionStateUnknown, idx ); else if( status.latin1() == (char)KMMsgNotEncrypted ) setEncryptionState( KMMsgNotEncrypted, idx ); else if( status.latin1() == (char)KMMsgPartiallyEncrypted ) setEncryptionState( KMMsgPartiallyEncrypted, idx ); else if( status.latin1() == (char)KMMsgFullyEncrypted ) setEncryptionState( KMMsgFullyEncrypted, idx ); else setEncryptionState( KMMsgEncryptionStateUnknown, idx ); } void KMMsgBase::setSignatureState( const KMMsgSignatureState status, int idx ) { kdDebug(5006) << "***setSignatureState1( " << status << " )" << endl; mDirty = TRUE; if (mParent) mParent->headerOfMsgChanged(this, idx); } void KMMsgBase::setMDNSentState( KMMsgMDNSentState, int idx ) { mDirty = true; if ( mParent ) mParent->headerOfMsgChanged(this, idx); } void KMMsgBase::setSignatureStateChar( QChar status, int idx ) { kdDebug(5006) << "***setSignatureState2( " << (status.isNull() ? '?' : status.latin1()) << " )" << endl; if( status.latin1() == (char)KMMsgSignatureStateUnknown ) setSignatureState( KMMsgSignatureStateUnknown, idx ); else if( status.latin1() == (char)KMMsgNotSigned ) setSignatureState( KMMsgNotSigned, idx ); else if( status.latin1() == (char)KMMsgPartiallySigned ) setSignatureState( KMMsgPartiallySigned,idx ); else if( status.latin1() == (char)KMMsgFullySigned ) setSignatureState( KMMsgFullySigned, idx ); else setSignatureState( KMMsgSignatureStateUnknown, idx ); } //----------------------------------------------------------------------------- bool KMMsgBase::isUnread(void) const { KMMsgStatus st = status(); return (st==KMMsgStatusNew || st==KMMsgStatusUnread); } //----------------------------------------------------------------------------- bool KMMsgBase::isNew(void) const { KMMsgStatus st = status(); return (st==KMMsgStatusNew); } //----------------------------------------------------------------------------- const char* KMMsgBase::statusToStr(KMMsgStatus aStatus) { static char sstr[2]; sstr[0] = (char)aStatus; sstr[1] = '\0'; return sstr; } //----------------------------------------------------------------------------- void KMMsgBase::setDate(const QCString& aDateStr) { setDate( KRFCDate::parseDate( aDateStr ) ); } //----------------------------------------------------------------------------- QString KMMsgBase::dateStr(void) const { time_t d = date(); return KMime::DateFormatter::formatDate(KMime::DateFormatter::Fancy, d); } //----------------------------------------------------------------------------- QString KMMsgBase::skipKeyword(const QString& aStr, QChar sepChar, bool* hasKeyword) { unsigned int i = 0, maxChars = 3; QString str = aStr; while (str[0] == ' ') str.remove(0,1); if (hasKeyword) *hasKeyword=FALSE; for (i=0; i < str.length() && i < maxChars; i++) { if (str[i] < 'A' || str[i] == sepChar) break; } if (str[i] == sepChar) // skip following spaces too { do { i++; } while (str[i] == ' '); if (hasKeyword) *hasKeyword=TRUE; return str.mid(i); } return str; } //----------------------------------------------------------------------------- QTextCodec* KMMsgBase::codecForName(const QCString& _str) { if (_str.isEmpty()) return 0; return KGlobal::charsets()->codecForName(_str.lower()); } //----------------------------------------------------------------------------- const QCString KMMsgBase::toUsAscii(const QString& _str, bool *ok) { bool all_ok =true; QString result = _str; int len = result.length(); for (int i = 0; i < len; i++) if (result.at(i).unicode() >= 128) { result.at(i) = '?'; all_ok = false; } if (ok) *ok = all_ok; return result.latin1(); } //----------------------------------------------------------------------------- QStringList KMMsgBase::supportedEncodings(bool usAscii) { QStringList encodingNames = KGlobal::charsets()->availableEncodingNames(); QStringList encodings; QMap mimeNames; for (QStringList::Iterator it = encodingNames.begin(); it != encodingNames.end(); it++) { QTextCodec *codec = KGlobal::charsets()->codecForName(*it); QString mimeName = (codec) ? QString(codec->mimeName()).lower() : (*it); if (mimeNames.find(mimeName) == mimeNames.end()) { encodings.append(KGlobal::charsets()->languageForEncoding(*it) + " ( " + mimeName + " )"); mimeNames.insert(mimeName, TRUE); } } encodings.sort(); if (usAscii) encodings.prepend(KGlobal::charsets() ->languageForEncoding("us-ascii") + " ( us-ascii )"); return encodings; } //----------------------------------------------------------------------------- QString KMMsgBase::decodeRFC2047String(const QCString& aStr) { QString result; QCString charset; char *pos, *beg, *end, *mid=0; QCString str, cstr, LWSP_buffer; char encoding='Q', ch; bool valid, lastWasEncodedWord=FALSE; const int maxLen=200; int i; if (aStr.find("=?") < 0) { QString str = kernel->networkCodec()->toUnicode(aStr); if (str.find('\n') == -1) return str; QString str2((QChar*)0, str.length()); uint i = 0; while (i < str.length()) { if (str[i] == '\n') { str2 += ' '; i += 2; } else { str2 += str[i]; i++; } } return str2; } for (pos=aStr.data(); *pos; pos++) { // line unfolding if ( pos[0] == '\r' && pos[1] == '\n' ) { pos++; continue; } if ( pos[0] == '\n' ) continue; // collect LWSP after encoded-words, // because we might need to throw it out // (when the next word is an encoded-word) if ( lastWasEncodedWord && ( pos[0] == ' ' || pos[0] == '\t' ) ) { LWSP_buffer += pos[0]; continue; } // verbatimly copy normal text if (pos[0]!='=' || pos[1]!='?') { result += LWSP_buffer + pos[0]; LWSP_buffer = 0; lastWasEncodedWord = FALSE; continue; } // found possible encoded-word beg = pos+2; end = beg; valid = TRUE; // parse charset name charset = ""; for (i=2,pos+=2; i=maxLen) valid = FALSE; else { // get encoding and check delimiting question marks encoding = toupper(pos[1]); if (pos[2]!='?' || (encoding!='Q' && encoding!='B')) valid = FALSE; pos+=3; i+=3; } if (valid) { mid = pos; // search for end of encoded part while (i=maxLen || !*pos) valid = FALSE; } if (valid) { // valid encoding: decode and throw away separating LWSP ch = *pos; *pos = '\0'; str = QCString(mid).left((int)(mid - pos - 1)); if (encoding == 'Q') { // decode quoted printable text for (i=str.length()-1; i>=0; i--) if (str[i]=='_') str[i]=' '; cstr = decodeQuotedPrintable(str); } else { // decode base64 text cstr = decodeBase64(str); } QTextCodec *codec = codecForName(charset); if (!codec) codec = kernel->networkCodec(); result += codec->toUnicode(cstr); lastWasEncodedWord = TRUE; *pos = ch; pos = end -1; } else { // invalid encoding, keep separating LWSP. //result += "=?"; //pos = beg -1; // because pos gets increased shortly afterwards pos = beg - 2; result += LWSP_buffer; result += *pos++; result += *pos; lastWasEncodedWord = FALSE; } LWSP_buffer = 0; } return result; } //----------------------------------------------------------------------------- const QCString especials = "()<>@,;:\"/[]?.= \033"; const QCString dontQuote = "\"()<>,@"; QCString KMMsgBase::encodeRFC2047Quoted(const QCString& aStr, bool base64) { if (base64) return encodeBase64(aStr).replace("\n",""); QCString result; unsigned char ch, hex; for (unsigned int i = 0; i < aStr.length(); i++) { ch = aStr.at(i); if (ch >= 128 || ch == '_' || especials.find(ch) != -1) { result += '='; hex = ((ch & 0xF0) >> 4) + 48; if (hex >= 58) hex += 7; result += hex; hex = (ch & 0x0F) + 48; if (hex >= 58) hex += 7; result += hex; } else { result += ch; } } return result; } QCString KMMsgBase::encodeRFC2047String(const QString& _str, const QCString& charset) { if (_str.isEmpty()) return QCString(); if (charset == "us-ascii") return toUsAscii(_str); QCString cset; if (charset.isEmpty()) cset = QCString(kernel->networkCodec()->mimeName()).lower(); else cset = charset; QTextCodec *codec = codecForName(cset); if (!codec) codec = kernel->networkCodec(); unsigned int nonAscii = 0; for (unsigned int i = 0; i < _str.length(); i++) if (_str.at(i).unicode() >= 128) nonAscii++; bool useBase64 = (nonAscii * 6 > _str.length()); unsigned int start, stop, p, pos = 0, encLength; QCString result; bool breakLine = FALSE; const unsigned int maxLen = 75 - 7 - cset.length(); while (pos < _str.length()) { start = pos; p = pos; while (p < _str.length()) { if (!breakLine && (_str.at(p) == ' ' || dontQuote.find(_str.at(p)) != -1)) start = p + 1; if (_str.at(p).unicode() >= 128 || _str.at(p) < ' ') break; p++; } if (breakLine || p < _str.length()) { while (dontQuote.find(_str.at(start)) != -1) start++; stop = start; while (stop < _str.length() && dontQuote.find(_str.at(stop)) == -1) stop++; result += _str.mid(pos, start - pos).latin1(); encLength = encodeRFC2047Quoted(codec->fromUnicode(_str. mid(start, stop - start)), useBase64).length(); breakLine = (encLength > maxLen); if (breakLine) { int dif = (stop - start) / 2; int step = dif; while (abs(step) > 1) { encLength = encodeRFC2047Quoted(codec->fromUnicode(_str. mid(start, dif)), useBase64).length(); step = (encLength > maxLen) ? (-abs(step) / 2) : (abs(step) / 2); dif += step; } stop = start + dif; } p = stop; while (p > start && _str.at(p) != ' ') p--; if (p > start) stop = p; if (result.right(3) == "?= ") start--; if (result.right(5) == "?=\n ") { start--; result.truncate(result.length() - 1); } int lastNewLine = result.findRev("\n "); if (!result.mid(lastNewLine).stripWhiteSpace().isEmpty() && result.length() - lastNewLine + encLength + 2 > maxLen) result += "\n "; result += "=?"; result += cset; result += (useBase64) ? "?b?" : "?q?"; result += encodeRFC2047Quoted(codec->fromUnicode(_str.mid(start, stop - start)), useBase64); result += "?="; if (breakLine) result += "\n "; pos = stop; } else { result += _str.mid(pos).latin1(); break; } } return result; } //----------------------------------------------------------------------------- QCString KMMsgBase::encodeRFC2231String(const QString& _str, const QCString& charset) { if (_str.isEmpty()) return QCString(); QCString cset; if (charset.isEmpty()) cset = QCString(kernel->networkCodec()->mimeName()).lower(); else cset = charset; QTextCodec *codec = codecForName(cset); QCString latin; if (charset == "us-ascii") latin = toUsAscii(_str); else if (codec) latin = codec->fromUnicode(_str); else latin = _str.local8Bit(); char *l = latin.data(); char hexcode; int i; bool quote; while (*l) { if (*l < 32) break; l++; } if (!*l) return latin; QCString result = cset + "''"; l = latin.data(); while (*l) { quote = *l < 0; for (i = 0; i < 17; i++) if (*l == especials[i]) quote = true; if (quote) { result += '%'; hexcode = ((*l & 0xF0) >> 4) + 48; if (hexcode >= 58) hexcode += 7; result += hexcode; hexcode = (*l & 0x0F) + 48; if (hexcode >= 58) hexcode += 7; result += hexcode; } else { result += *l; } l++; } return result; } //----------------------------------------------------------------------------- QString KMMsgBase::decodeRFC2231String(const QCString& _str) { int p = _str.find('\''); if (p < 0) return kernel->networkCodec()->toUnicode(_str); QCString charset = _str.left(p); QCString st = _str.mid(_str.findRev('\'') + 1); char ch, ch2; p = 0; while (p < (int)st.length()) { if (st.at(p) == 37) { ch = st.at(p+1) - 48; if (ch > 16) ch -= 7; ch2 = st.at(p+2) - 48; if (ch2 > 16) ch2 -= 7; st.at(p) = ch * 16 + ch2; st.remove( p+1, 2 ); } p++; } QString result; QTextCodec *codec = codecForName(charset); if (!codec) codec = kernel->networkCodec(); if (codec) result = codec->toUnicode(st); else result = kernel->networkCodec()->toUnicode(st); return result; } //----------------------------------------------------------------------------- QCString KMMsgBase::autoDetectCharset(const QCString &_encoding, const QStringList &encodingList, const QString &text) { QStringList charsets = encodingList; if (!_encoding.isEmpty()) { QString currentCharset = QString::fromLatin1(_encoding); charsets.remove(currentCharset); charsets.prepend(currentCharset); } QStringList::ConstIterator it = charsets.begin(); for (; it != charsets.end(); ++it) { QCString encoding = (*it).latin1(); if (encoding == "locale") encoding = QCString(kernel->networkCodec()->mimeName()).lower(); if (text.isEmpty()) return encoding; if (encoding == "us-ascii") { bool ok; (void) KMMsgBase::toUsAscii(text, &ok); if (ok) return encoding; } else { QTextCodec *codec = KMMsgBase::codecForName(encoding); if (!codec) { kdDebug(5006) << "Auto-Charset: Something is wrong and I can not get a codec. [" << encoding << "]" << endl; } else { if (codec->canEncode(text)) return encoding; } } } return 0; } //----------------------------------------------------------------------------- QCString KMMsgBase::decodeQuotedPrintable(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; DwString dwsrc(bStr.data()); DwString dwdest; DwDecodeQuotedPrintable(dwsrc, dwdest); return dwdest.c_str(); } //----------------------------------------------------------------------------- QCString KMMsgBase::encodeQuotedPrintable(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; DwString dwsrc(bStr.data(), bStr.length()); DwString dwdest; QCString result; DwEncodeQuotedPrintable(dwsrc, dwdest); result = dwdest.c_str(); return result; } //----------------------------------------------------------------------------- QCString KMMsgBase::decodeBase64(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; while (bStr.length() < 16) bStr += '='; DwString dwsrc(bStr.data(), bStr.length()); DwString dwdest; QCString result; DwDecodeBase64(dwsrc, dwdest); result = dwdest.c_str(); return result; } //----------------------------------------------------------------------------- QCString KMMsgBase::encodeBase64(const QCString& aStr) { QCString bStr = aStr; if (aStr.isNull()) bStr = ""; DwString dwsrc(bStr.data(), bStr.length()); DwString dwdest; QCString result; DwEncodeBase64(dwsrc, dwdest); result = dwdest.c_str(); return result; } //----------------------------------------------------------------------------- unsigned long KMMsgBase::getMsgSerNum() const { unsigned long msn = 0; if (mParent) { int index = mParent->find((KMMsgBase*)this); msn = kernel->msgDict()->getMsgSerNum(mParent, index); } return msn; } //----------------------------------------------------------------------------- static void swapEndian(QString &str) { ushort us; uint len = str.length(); for (uint i = 0; i < len; i++) { us = str[i].unicode(); str[i] = QChar(kmail_swap_16(us)); } } //----------------------------------------------------------------------------- static int g_chunk_length = 0, g_chunk_offset=0; static uchar *g_chunk = 0; #define COPY_DATA(x, length) do { \ if(g_chunk_offset + ((int)length) > g_chunk_length) {\ g_chunk_offset = g_chunk_length; \ kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl; \ memset(x, length, '\0'); \ } else { \ memcpy(x, g_chunk+g_chunk_offset, length); \ g_chunk_offset += length; \ } } while(0) #define COPY_HEADER_TYPE(x) Q_ASSERT(sizeof(x) == sizeof(Q_UINT32)); COPY_DATA(&x, sizeof(x)); #define COPY_HEADER_LEN(x) Q_ASSERT(sizeof(x) == sizeof(Q_UINT16)); COPY_DATA(&x, sizeof(x)); //----------------------------------------------------------------------------- QString KMMsgBase::getStringPart(MsgPartType t) const { QString ret(""); g_chunk_offset = 0; bool using_mmap = FALSE; bool swapByteOrder = mParent->indexSwapByteOrder(); if (mParent->indexStreamBasePtr()) { if (g_chunk) free(g_chunk); using_mmap = TRUE; g_chunk = mParent->indexStreamBasePtr() + mIndexOffset; g_chunk_length = mIndexLength; } else { if(!mParent->mIndexStream) return ret; if (g_chunk_length < mIndexLength) g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength); off_t first_off=ftell(mParent->mIndexStream); fseek(mParent->mIndexStream, mIndexOffset, SEEK_SET); fread( g_chunk, mIndexLength, 1, mParent->mIndexStream); fseek(mParent->mIndexStream, first_off, SEEK_SET); } MsgPartType type; Q_UINT16 l; while(g_chunk_offset < mIndexLength) { Q_UINT32 tmp; COPY_HEADER_TYPE(tmp); COPY_HEADER_LEN(l); if (swapByteOrder) { tmp = kmail_swap_32(tmp); l = kmail_swap_16(l); } type = (MsgPartType) tmp; if(g_chunk_offset + l > mIndexLength) { kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl; break; } if(type == t) { // This works because the QString constructor does a memcpy. // Otherwise we would need to be concerned about the alignment. if(l) ret = QString((QChar *)(g_chunk + g_chunk_offset), l/2); break; } g_chunk_offset += l; } if(using_mmap) { g_chunk_length = 0; g_chunk = 0; } // Normally we need to swap the byte order because the QStrings are written // in the style of Qt2 (MSB -> network ordered). // QStrings in Qt3 expect host ordering. // On e.g. Intel host ordering is LSB, on e.g. Sparc it is MSB. #ifndef WORDS_BIGENDIAN // #warning Byte order is little endian (swap is true) swapEndian(ret); #else // #warning Byte order is big endian (swap is false) #endif return ret; } //----------------------------------------------------------------------------- off_t KMMsgBase::getLongPart(MsgPartType t) const { off_t ret = 0; g_chunk_offset = 0; bool using_mmap = FALSE; int sizeOfLong = mParent->indexSizeOfLong(); bool swapByteOrder = mParent->indexSwapByteOrder(); if (mParent->indexStreamBasePtr()) { if (g_chunk) free(g_chunk); using_mmap = TRUE; g_chunk = mParent->indexStreamBasePtr() + mIndexOffset; g_chunk_length = mIndexLength; } else { if (!mParent->mIndexStream) return ret; assert(mIndexLength >= 0); if (g_chunk_length < mIndexLength) g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength); off_t first_off=ftell(mParent->mIndexStream); fseek(mParent->mIndexStream, mIndexOffset, SEEK_SET); fread( g_chunk, mIndexLength, 1, mParent->mIndexStream); fseek(mParent->mIndexStream, first_off, SEEK_SET); } MsgPartType type; Q_UINT16 l; while (g_chunk_offset < mIndexLength) { Q_UINT32 tmp; COPY_HEADER_TYPE(tmp); COPY_HEADER_LEN(l); if (swapByteOrder) { tmp = kmail_swap_32(tmp); l = kmail_swap_16(l); } type = (MsgPartType) tmp; if (g_chunk_offset + l > mIndexLength) { kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl; break; } if(type == t) { assert(sizeOfLong == l); if (sizeOfLong == sizeof(ret)) { COPY_DATA(&ret, sizeof(ret)); if (swapByteOrder) { if (sizeof(ret) == 4) ret = kmail_swap_32(ret); else ret = kmail_swap_64(ret); } } else if (sizeOfLong == 4) { // Long is stored as 4 bytes in index file, sizeof(long) = 8 Q_UINT32 ret_32; COPY_DATA(&ret_32, sizeof(ret_32)); if (swapByteOrder) ret_32 = kmail_swap_32(ret_32); ret = ret_32; } else if (sizeOfLong == 8) { // Long is stored as 8 bytes in index file, sizeof(long) = 4 Q_UINT32 ret_1; Q_UINT32 ret_2; COPY_DATA(&ret_1, sizeof(ret_1)); COPY_DATA(&ret_2, sizeof(ret_2)); if (!swapByteOrder) { // Index file order is the same as the order of this CPU. #ifndef WORDS_BIGENDIAN // Index file order is little endian ret = ret_1; // We drop the 4 most significant bytes #else // Index file order is big endian ret = ret_2; // We drop the 4 most significant bytes #endif } else { // Index file order is different from this CPU. #ifndef WORDS_BIGENDIAN // Index file order is big endian ret = ret_2; // We drop the 4 most significant bytes #else // Index file order is little endian ret = ret_1; // We drop the 4 most significant bytes #endif // We swap the result to host order. ret = kmail_swap_32(ret); } } break; } g_chunk_offset += l; } if(using_mmap) { g_chunk_length = 0; g_chunk = 0; } return ret; } #undef COPY_DATA //----------------------------------------------------------------------------- const uchar *KMMsgBase::asIndexString(int &length) const { unsigned int csize = 256; static uchar *ret = 0; //different static buffer here for we may use the other buffer in the functions below if(!ret) ret = (uchar *)malloc(csize); length = 0; #ifndef WORDS_BIGENDIAN // We need to use swab to swap bytes to network byte order #define memcpy_networkorder(to, from, len) swab(from, to, len) #else // We're already in network byte order #define memcpy_networkorder(to, from, len) memcpy(to, from, len) #endif #define STORE_DATA_LEN(type, x, len, network_order) do { \ int len2 = (len > 256) ? 256 : len; \ if(csize < (length + (len2 + sizeof(short) + sizeof(MsgPartType)))) \ ret = (uchar *)realloc(ret, csize += len2+sizeof(short)+sizeof(MsgPartType)); \ Q_UINT32 t = (Q_UINT32) type; memcpy(ret+length, &t, sizeof(t)); \ Q_UINT16 l = len2; memcpy(ret+length+sizeof(t), &l, sizeof(l)); \ if (network_order) \ memcpy_networkorder(ret+length+sizeof(t)+sizeof(l), x, len2); \ else \ memcpy(ret+length+sizeof(t)+sizeof(l), x, len2); \ length += len2+sizeof(t)+sizeof(l); \ } while(0) #define STORE_DATA(type, x) STORE_DATA_LEN(type, &x, sizeof(x), false) unsigned long tmp; QString tmp_str; //these is at the beginning because it is queried quite often tmp_str = msgIdMD5().stripWhiteSpace(); STORE_DATA_LEN(MsgIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); tmp = status(); STORE_DATA(MsgStatusPart, tmp); //these are completely arbitrary order tmp_str = fromStrip().stripWhiteSpace(); STORE_DATA_LEN(MsgFromPart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = subject().stripWhiteSpace(); STORE_DATA_LEN(MsgSubjectPart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = toStrip().stripWhiteSpace(); STORE_DATA_LEN(MsgToPart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = replyToIdMD5().stripWhiteSpace(); STORE_DATA_LEN(MsgReplyToIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = xmark().stripWhiteSpace(); STORE_DATA_LEN(MsgXMarkPart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = fileName().stripWhiteSpace(); STORE_DATA_LEN(MsgFilePart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp = msgSize(); STORE_DATA(MsgSizePart, tmp); tmp = folderOffset(); STORE_DATA(MsgOffsetPart, tmp); tmp = date(); STORE_DATA(MsgDatePart, tmp); tmp = (signatureState() << 16) | encryptionState(); STORE_DATA(MsgCryptoStatePart, tmp); tmp = mdnSentState(); STORE_DATA(MsgMDNSentPart, tmp); + + tmp_str = replyToAuxIdMD5().stripWhiteSpace(); + STORE_DATA_LEN(MsgReplyToAuxIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); + + tmp_str = strippedSubjectMD5().stripWhiteSpace(); + STORE_DATA_LEN(MsgStrippedSubjectMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); + #undef STORE_DATA_LEN return ret; } bool KMMsgBase::syncIndexString() const { if(!dirty()) return TRUE; int len; const uchar *buffer = asIndexString(len); if (len == mIndexLength) { Q_ASSERT(mParent->mIndexStream); fseek(mParent->mIndexStream, mIndexOffset, SEEK_SET); fwrite( buffer, len, 1, mParent->mIndexStream); return TRUE; } return FALSE; }