File indexing completed on 2024-04-28 05:18:35

0001 /*
0002   SPDX-FileCopyrightText: 2009 Bertjan Broeksema <broeksema@kde.org>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mbox_p.h"
0008 
0009 #include "kmbox_debug.h"
0010 #include <QLocale>
0011 #include <QUrl>
0012 
0013 using namespace KMBox;
0014 
0015 const QRegularExpression MBoxPrivate::mSeparatorMatcher(QStringLiteral("^From .*[0-9][0-9]:[0-9][0-9]"));
0016 
0017 MBoxPrivate::MBoxPrivate(MBox *mbox)
0018     : mMBox(mbox)
0019     , mLockType(MBox::None)
0020 {
0021     connect(&mUnlockTimer, &QTimer::timeout, this, &MBoxPrivate::unlockMBox);
0022 }
0023 
0024 MBoxPrivate::~MBoxPrivate()
0025 {
0026     if (mMboxFile.isOpen()) {
0027         mMboxFile.close();
0028     }
0029 }
0030 
0031 bool MBoxPrivate::open()
0032 {
0033     if (mMboxFile.isOpen()) {
0034         return true; // already open
0035     }
0036 
0037     QIODevice::OpenMode mode = mReadOnly ? QIODevice::ReadOnly : QIODevice::ReadWrite;
0038 
0039     if (!mMboxFile.open(mode)) { // messages file
0040         // failed to open readWrite -> try to open readOnly
0041         if (!mMboxFile.open(QIODevice::ReadOnly)) {
0042             qCDebug(KMBOX_LOG) << "Cannot open mbox file `" << mMboxFile.fileName() << "' FileError:" << mMboxFile.errorString();
0043             return false;
0044         } else {
0045             mReadOnly = true;
0046         }
0047     }
0048 
0049     return true;
0050 }
0051 
0052 void MBoxPrivate::close()
0053 {
0054     if (mMboxFile.isOpen()) {
0055         mMboxFile.close();
0056     }
0057 
0058     mFileLocked = false;
0059 }
0060 
0061 void MBoxPrivate::initLoad(const QString &fileName)
0062 {
0063     QUrl url = QUrl::fromLocalFile(fileName);
0064     mMboxFile.setFileName(url.toLocalFile());
0065     mAppendedEntries.clear();
0066     mEntries.clear();
0067 }
0068 
0069 bool MBoxPrivate::startTimerIfNeeded()
0070 {
0071     if (mUnlockTimer.interval() > 0) {
0072         mUnlockTimer.start();
0073         return true;
0074     }
0075 
0076     return false;
0077 }
0078 
0079 void MBoxPrivate::unlockMBox()
0080 {
0081     mMBox->unlock();
0082 }
0083 
0084 QByteArray MBoxPrivate::mboxMessageSeparator(const QByteArray &msg)
0085 {
0086     KMime::Message mail;
0087     QByteArray body;
0088     QByteArray header;
0089     KMime::HeaderParsing::extractHeaderAndBody(KMime::CRLFtoLF(msg), header, body);
0090     body.clear();
0091     mail.setHead(header);
0092     mail.parse();
0093 
0094     QByteArray separator = "From ";
0095 
0096     KMime::Headers::From *from = mail.from(false);
0097     if (!from || from->addresses().isEmpty()) {
0098         separator += "unknown@unknown.invalid";
0099     } else {
0100         separator += from->addresses().at(0) + ' ';
0101     }
0102 
0103     // format dateTime according to the mbox "standard" RFC4155
0104     KMime::Headers::Date *date = mail.date(false);
0105     QDateTime dateTime;
0106     if (!date || date->isEmpty()) {
0107         dateTime = QDateTime::currentDateTimeUtc();
0108     } else {
0109         dateTime = date->dateTime().toUTC();
0110     }
0111     separator += QLocale::c().toString(dateTime, QStringLiteral("ddd MMM dd HH:mm:ss yyyy")).toUtf8() + '\n';
0112 
0113     return separator;
0114 }
0115 
0116 #define STRDIM(x) (sizeof(x) / sizeof(*x) - 1)
0117 
0118 QByteArray MBoxPrivate::escapeFrom(const QByteArray &str)
0119 {
0120     const unsigned int strLen = str.length();
0121     if (strLen <= STRDIM("From ")) {
0122         return str;
0123     }
0124 
0125     // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6
0126     QByteArray result(int(strLen + 5) / 6 * 7 + 1, '\0');
0127 
0128     const char *s = str.data();
0129     const char *const e = s + strLen - STRDIM("From ");
0130     char *d = result.data();
0131 
0132     bool onlyAnglesAfterLF = false; // don't match ^From_
0133     while (s < e) {
0134         switch (*s) {
0135         case '\n':
0136             onlyAnglesAfterLF = true;
0137             break;
0138         case '>':
0139             break;
0140         case 'F':
0141             if (onlyAnglesAfterLF && qstrncmp(s + 1, "rom ", STRDIM("rom ")) == 0) {
0142                 *d++ = '>';
0143             }
0144             // fall through
0145             [[fallthrough]];
0146         default:
0147             onlyAnglesAfterLF = false;
0148             break;
0149         }
0150         *d++ = *s++;
0151     }
0152 
0153     while (s < str.data() + strLen) {
0154         *d++ = *s++;
0155     }
0156 
0157     result.truncate(d - result.data());
0158 
0159     return result;
0160 }
0161 
0162 // performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion
0163 void MBoxPrivate::unescapeFrom(char *str, size_t strLen)
0164 {
0165     if (!str) {
0166         return;
0167     }
0168 
0169     if (strLen <= STRDIM(">From ")) {
0170         return;
0171     }
0172 
0173     // yes, *d++ = *s++ is a no-op as long as d == s (until after the
0174     // first >From_), but writes are cheap compared to reads and the
0175     // data is already in the cache from the read, so special-casing
0176     // might even be slower...
0177     const char *s = str;
0178     char *d = str;
0179     const char *const e = str + strLen - STRDIM(">From ");
0180 
0181     while (s < e) {
0182         if (*s == '\n' && *(s + 1) == '>') { // we can do the lookahead,
0183             // since e is 6 chars from the end!
0184             *d++ = *s++; // == '\n'
0185             *d++ = *s++; // == '>'
0186 
0187             while (s < e && *s == '>') {
0188                 *d++ = *s++;
0189             }
0190 
0191             if (qstrncmp(s, "From ", STRDIM("From ")) == 0) {
0192                 --d;
0193             }
0194         }
0195 
0196         *d++ = *s++; // yes, s might be e here, but e is not the end :-)
0197     }
0198     // copy the rest:
0199     while (s < str + strLen) {
0200         *d++ = *s++;
0201     }
0202 
0203     if (d < s) { // only NUL-terminate if it's shorter
0204         *d = 0;
0205     }
0206 }
0207 
0208 bool MBoxPrivate::isMBoxSeparator(const QByteArray &line) const
0209 {
0210     if (!line.startsWith("From ")) { // krazy:exclude=strings
0211         return false;
0212     }
0213 
0214     return mSeparatorMatcher.match(QLatin1StringView(line)).hasMatch();
0215 }
0216 
0217 #undef STRDIM
0218 
0219 #include "moc_mbox_p.cpp"