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"