diff --git a/kmbox/mbox.cpp b/kmbox/mbox.cpp index 6acb44a46..b066039a3 100644 --- a/kmbox/mbox.cpp +++ b/kmbox/mbox.cpp @@ -1,507 +1,507 @@ /* Copyright (c) 1996-1998 Stefan Taferner Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. NOTE: Most of the code inside here is an slightly adjusted version of kdepim/kmail/kmfoldermbox.cpp. This is why I added a copyright line for Stefan Taferner. Bertjan Broeksema, april 2009 */ #include "mbox.h" #include #include #include #include #include #include #include #include #include class MBox::Private { public: Private(const QString &mboxFileName, bool readOnly) : mLock(mboxFileName) , mMboxFile(mboxFileName) , mReadOnly(readOnly) { } ~Private() { if (mMboxFile.isOpen()) mMboxFile.close(); } bool mFileLocked; KLockFile mLock; LockType mLockType; QFile mMboxFile; QString mProcmailLockFileName; bool mReadOnly; }; /// private static methods. QByteArray quoteAndEncode(const QString &str) { return QFile::encodeName(KShell::quoteArg(str)); } /// public methods. MBox::MBox(const QString &mboxFile, bool readOnly) : d(new Private(mboxFile, readOnly)) { // Set some sane defaults d->mFileLocked = false; d->mLockType = KDELockFile; } MBox::~MBox() { close(); delete d; } void MBox::close() { if (d->mFileLocked) unlock(); if (d->mMboxFile.isOpen()) d->mMboxFile.close(); d->mFileLocked = false; } -QList MBox::entryList(const QSet &deletedItems) const +QList MBox::entryList(const QSet &deletedItems) const { Q_ASSERT(d->mMboxFile.isOpen()); QRegExp regexp("^From .*[0-9][0-9]:[0-9][0-9]"); QByteArray line; quint64 offs = 0; // The offset of the next message to read. QList result; while (!d->mMboxFile.atEnd()) { quint64 pos = d->mMboxFile.pos(); line = d->mMboxFile.readLine(); if (regexp.indexIn(line) >= 0 || d->mMboxFile.atEnd()) { // Found the separator or at end of file, the message starts at offs quint64 msgSize = pos - offs; if(pos > 0 && !deletedItems.contains(offs)) { // This is not the separator of the first mail in the file. If pos == 0 // than we matched the separator of the first mail in the file. MsgInfo info; info.first = offs; info.second = msgSize; result << info; offs += msgSize; // Mark the beginning of the next message. } } } return result; } bool MBox::isValid() const { QString msg; return isValid(msg); } bool MBox::isValid(QString &errorMsg) const { QFileInfo info(d->mMboxFile); if (!info.isFile()) { errorMsg = i18n("%1 is not a file.").arg(info.absoluteFilePath()); return false; } if (!info.exists()) { errorMsg = i18n("%1 does not exist").arg(info.absoluteFilePath()); return false; } switch (d->mLockType) { case ProcmailLockfile: if (KStandardDirs::findExe("lockfile").isEmpty()) { errorMsg = i18n("Could not find the lockfile executable"); return false; } break; case MuttDotlock: // fall through case MuttDotlockPrivileged: if (KStandardDirs::findExe("mutt_dotlock").isEmpty()) { errorMsg = i18n("Could not find the mutt_dotlock executable"); return false; } break; default: break; // We assume fcntl available and lock_none doesn't need a check. } // TODO: Add some heuristics to see if the file actually is a mbox file. return true; } int MBox::open(OpenMode openMode) { if (d->mMboxFile.isOpen() && openMode == Normal) { return 0; // already open } else if (d->mMboxFile.isOpen()) { close(); // ReloadMode, so close the file first. } d->mFileLocked = false; if (int rc = lock()) { kDebug() << "Locking of the mbox file failed."; return rc; } if (!d->mMboxFile.open(QIODevice::ReadWrite)) { // messages file kDebug() << "Cannot open mbox file `" << d->mMboxFile.fileName() << "' FileError:" << d->mMboxFile.error(); return d->mMboxFile.error(); } return 0; } QByteArray MBox::readEntry(quint64 offset) const { Q_ASSERT(d->mMboxFile.isOpen()); Q_ASSERT(d->mMboxFile.size() > 0); Q_ASSERT(static_cast(d->mMboxFile.size()) > offset); d->mMboxFile.seek(offset); QByteArray line = d->mMboxFile.readLine(); QRegExp regexp("^From .*[0-9][0-9]:[0-9][0-9]"); if (regexp.indexIn(line) < 0) return QByteArray(); // The file is messed up or the index is incorrect. QByteArray message; message += line; line = d->mMboxFile.readLine(); while (regexp.indexIn(line) < 0 && !d->mMboxFile.atEnd()) { message += line; line = d->mMboxFile.readLine(); } unescapeFrom(message.data(), message.size()); return message; } QByteArray MBox::readEntryHeaders(quint64 offset) { Q_ASSERT(d->mMboxFile.isOpen()); Q_ASSERT(d->mMboxFile.size() > 0); Q_ASSERT(static_cast(d->mMboxFile.size()) > offset); d->mMboxFile.seek(offset); QByteArray headers; QByteArray line = d->mMboxFile.readLine(); while (!line[0] == '\n') { headers += line; line = d->mMboxFile.readLine(); } return headers; } void MBox::setLockType(LockType ltype) { if (d->mFileLocked) return; // Don't change the method if the file is currently locked. d->mLockType = ltype; } void MBox::setProcmailLockFile(const QString &lockFile) { d->mProcmailLockFileName = lockFile; } qint64 MBox::writeEntry(const QByteArray &entry) { QByteArray msgText = escapeFrom(entry); if (msgText.size() <= 0) { kDebug() << "Message added to folder `" << d->mMboxFile.fileName() << "' contains no data. Ignoring it."; return -1; } int nextOffset = d->mMboxFile.size(); // Offset of the appended message // Make sure the file is large enough to check for an end character. Then check // if the required newlines are there. if (nextOffset >= 2) { d->mMboxFile.seek(nextOffset - 2); char endStr[3]; d->mMboxFile.read(endStr, 2); if (d->mMboxFile.pos() > 0 && endStr[0] != '\n') { if ( endStr[1]!='\n' ) { d->mMboxFile.write("\n\n"); nextOffset += 2; } else { d->mMboxFile.write("\n"); ++nextOffset; } } } d->mMboxFile.write(mboxMessageSeparator(entry)); d->mMboxFile.write(entry); if (entry[entry.size() - 1] != '\n' ) { d->mMboxFile.write("\n\n"); } if(!d->mMboxFile.flush()) return -1; // TODO Some proper error handling when writing fails. return nextOffset; } /// private methods int MBox::lock() { if (d->mLockType == None) return 0; d->mFileLocked = false; QStringList args; int rc = 0; switch(d->mLockType) { case KDELockFile: /* FIXME: Don't use the mbox file itself as lock file. if ((rc = d->mLock.lock(KLockFile::ForceFlag))) { kDebug() << "KLockFile lock failed: (" << rc << ") switching to read only mode"; d->mReadOnly = true; } */ return 0; break; // We only need to lock the file using the QReadWriteLock case ProcmailLockfile: args << "-l20" << "-r5"; if (!d->mProcmailLockFileName.isEmpty()) args << quoteAndEncode(d->mProcmailLockFileName); else args << quoteAndEncode(d->mMboxFile.fileName() + ".lock"); rc = QProcess::execute("lockfile", args); if(rc != 0) { kDebug() << "lockfile -l20 -r5 " << d->mMboxFile.fileName() << ": Failed ("<< rc << ") switching to read only mode"; d->mReadOnly = true; // In case the MBox object was created read/write we // set it to read only when locking failed. return rc; } break; case MuttDotlock: args << quoteAndEncode(d->mMboxFile.fileName()); rc = QProcess::execute("mutt_dotlock", args); if(rc != 0) { kDebug() << "mutt_dotlock " << d->mMboxFile.fileName() << ": Failed (" << rc << ") switching to read only mode"; d->mReadOnly = true; // In case the MBox object was created read/write we // set it to read only when locking failed. return rc; } break; case MuttDotlockPrivileged: args << "-p" << quoteAndEncode(d->mMboxFile.fileName()); rc = QProcess::execute("mutt_dotlock", args); if(rc != 0) { kDebug() << "mutt_dotlock -p " << d->mMboxFile.fileName() << ":" << ": Failed (" << rc << ") switching to read only mode"; d->mReadOnly = true; return rc; } break; case None: // This is never reached because of the check at the return 0; // beginning of the function. default: break; } d->mFileLocked = true; return 0; } int MBox::unlock() { int rc = 0; QStringList args; switch(d->mLockType) { case KDELockFile: // FIXME //d->mLock.unlock(); break; case ProcmailLockfile: // QFile::remove returns true on succes so negate the result. if (!d->mProcmailLockFileName.isEmpty()) rc = !QFile(d->mProcmailLockFileName).remove(); else rc = !QFile(d->mMboxFile.fileName() + ".lock").remove(); break; case MuttDotlock: args << "-u" << quoteAndEncode(d->mMboxFile.fileName()); rc = QProcess::execute("mutt_dotlock", args); break; case MuttDotlockPrivileged: args << "-u" << "-p" << quoteAndEncode(d->mMboxFile.fileName()); rc = QProcess::execute("mutt_dotlock", args); break; case None: // Fall through. default: break; } if (!rc) // Unlocking succeeded d->mFileLocked = false; return rc; } QByteArray MBox::mboxMessageSeparator(const QByteArray &msg) { KMime::Message mail; mail.setHead(KMime::CRLFtoLF(msg)); mail.parse(); QByteArray seperator = "From "; KMime::Headers::From *from = mail.from(false); if (!from || from->addresses().isEmpty()) seperator += "unknown@unknown.invalid"; else seperator += from->addresses().first(); seperator += QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8() + '\n'; return seperator; } #define STRDIM(x) (sizeof(x)/sizeof(*x)-1) QByteArray MBox::escapeFrom(const QByteArray &str) { const unsigned int strLen = str.length(); if ( strLen <= STRDIM("From ") ) return str; // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6 QByteArray result(int( strLen + 5 ) / 6 * 7 + 1, '\0'); const char * s = str.data(); const char * const e = s + strLen - STRDIM("From "); char * d = result.data(); bool onlyAnglesAfterLF = false; // dont' match ^From_ while ( s < e ) { switch ( *s ) { case '\n': onlyAnglesAfterLF = true; break; case '>': break; case 'F': if ( onlyAnglesAfterLF && qstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 ) *d++ = '>'; // fall through default: onlyAnglesAfterLF = false; break; } *d++ = *s++; } while ( s < str.data() + strLen ) *d++ = *s++; result.truncate( d - result.data() ); return result; } // performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion void MBox::unescapeFrom(char* str, size_t strLen) { if (!str) return; if ( strLen <= STRDIM(">From ") ) return; // yes, *d++ = *s++ is a no-op as long as d == s (until after the // first >From_), but writes are cheap compared to reads and the // data is already in the cache from the read, so special-casing // might even be slower... const char * s = str; char * d = str; const char * const e = str + strLen - STRDIM(">From "); while ( s < e ) { if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end! *d++ = *s++; // == '\n' *d++ = *s++; // == '>' while ( s < e && *s == '>' ) *d++ = *s++; if ( qstrncmp( s, "From ", STRDIM("From ") ) == 0 ) --d; } *d++ = *s++; // yes, s might be e here, but e is not the end :-) } // copy the rest: while ( s < str + strLen ) *d++ = *s++; if ( d < s ) // only NUL-terminate if it's shorter *d = 0; } #undef STRDIM diff --git a/kmbox/mbox.h b/kmbox/mbox.h index 313cff5c2..ebc5aaba0 100644 --- a/kmbox/mbox.h +++ b/kmbox/mbox.h @@ -1,164 +1,164 @@ /* Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MBOX_H #define MBOX_H #include #include #include "mbox_export.h" typedef QPair MsgInfo; // QPair class MBOX_EXPORT MBox { public: enum OpenMode { Normal, Reload }; enum LockType { KDELockFile, // Uses KLockFile ProcmailLockfile, MuttDotlock, MuttDotlockPrivileged, None }; public: explicit MBox(const QString &mboxFile = QString(), bool readOnly = false); /** * Closes the file if it is still open. */ ~MBox(); /** * Closes the file and releases the lock. */ void close(); /** * Retrieve MsgInfo objects for all emails from the file except the * @param deleteItems. The @param deletedItems should be a list of file * offsets of messages which are deleted. * * Each MsgInfo object contains the offset and the size of the messages in * the file which are not marked as deleted. * * Note: One must call open() before calling this method. */ - QList entryList(const QSet &deletedItems = QSet()) const; + QList entryList(const QSet &deletedItems = QSet()) const; /** * Checks if the file exists and if it can be opened for read/write. Also * checks if the selected lock method is available when it is set to * procmail_lockfile or one of the mutt_dotlock variants. */ bool isValid() const; /** * @see isValid() * @param errorMsg can be used to find out what kind of error occurred and * passed onto the user. */ bool isValid(QString &errorMsg) const; /** * Open folder for access. Does nothing if the folder is already opened * and openMode equals Normal (default behavior). When Reload is given as * open mode the file will be closed first if it is open. * * Returns zero on success and an error code equal to the c-library fopen * call otherwise (errno). */ int open(OpenMode openMode = Normal); /** * Reads the entire message from the file at given @param offset. */ QByteArray readEntry(quint64 offset) const; /** * Reads the headers of the message at given @param offset. */ QByteArray readEntryHeaders(quint64 offset); /** * Sets the locktype that should be used for locking the mbox file. The * isValid method will check if the lock method is available when the * procmail_lockfile or one of the mutt_dotlock variants is set. * * This method will not do anything if the mbox obeject is currently locked * to make sure that it doesn't leave a locked file for one of the lockfile * / mutt_dotlock methods. */ void setLockType(LockType ltype); /** * Sets the lockfile that should be used by the procmail lock file method. * If this method is not called and procfile is used the name of the lock * file will be equal to MBOXFILENAME.lock. */ void setProcmailLockFile(const QString &lockFile); /** * Appends @param entry to the mbox file. Returns the offset in the file * where the added message starts or -1 if the message was not added. */ qint64 writeEntry(const QByteArray &entry); private: static QByteArray escapeFrom(const QByteArray &msg); /** * Locks the mbox file. Called by open(). Returns 0 on success and an errno * error code on failure. * * NOTE: This method will set the MBox object to ReadOnly mode when locking * failed to prevent data corruption, even when the MBox was originally * opened ReadWrite. */ int lock(); /** * Generates a mbox message sperator line for given message. */ static QByteArray mboxMessageSeparator(const QByteArray &msg); /** * Unlock the mbox file. Called by close() or ~MBox(). Returns 0 on success * and an errno error code on failure. */ int unlock(); /** * Unescapes the raw message read from the file. */ static void unescapeFrom(char *msg, size_t size); private: class Private; Private *d; }; #endif // MBOX_H