MailImporter

filteroe.cpp
1/*
2 filter_oe.cpp - Outlook Express mail import
3
4 SPDX-FileCopyrightText: 2003 Laurence Anderson <l.d.anderson@warwick.ac.uk>
5 SPDX-FileCopyrightText: 2005 Danny Kukawka <danny.Kukawka@web.de>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10// This filter was created by looking at libdbx &liboe
11
12#include "filteroe.h"
13#include "mailimporter_debug.h"
14
15#include <KLocalizedString>
16#include <QFileDialog>
17
18#include <QTemporaryFile>
19
20#define OE4_SIG_1 0x36464d4a
21#define OE4_SIG_2 0x00010003
22#define OE5_SIG_1 0xfe12adcf
23#define OE5_EMAIL_SIG_2 0x6f74fdc5
24#define OE5_FOLDER_SIG_2 0x6f74fdc6
25#define OE5_SIG_3 0x11d1e366
26#define OE5_SIG_4 0xc0004e9a
27#define MBX_MAILMAGIC 0x7F007F00
28
29using namespace MailImporter;
30
31FilterOE::FilterOE()
32 : Filter(i18n("Import Outlook Express Emails"),
33 i18n("Laurence Anderson <br>( Filter enhanced by Danny Kukawka )</p>"),
34 i18n("<p><b>Outlook Express 4/5/6 import filter</b></p>"
35 "<p>You will need to locate the folder where the mailbox has been "
36 "stored by searching for .dbx or .mbx files under "
37 "<ul><li><i>C:\\Windows\\Application Data</i> in Windows 9x</li>"
38 "<li><i>Documents and Settings</i> in Windows 2000 or later</li></ul></p>"
39 "<p><b>Note:</b> Since it is possible to recreate the folder structure, the folders from "
40 "Outlook Express 5 and 6 will be stored under: \"OE-Import\" in your local folder.</p>"))
41{
42}
43
44FilterOE::~FilterOE()
45{
46}
47
48void FilterOE::import()
49{
50 // Select directory containing plain text emails
51 const QString maildir = QFileDialog::getExistingDirectory(filterInfo()->parentWidget(), QString(), QDir::homePath());
52 importMails(maildir);
53}
54
55void FilterOE::importMails(const QString &maildir)
56{
57 if (maildir.isEmpty()) { // No directory selected
58 filterInfo()->alert(i18n("No directory selected."));
59 return;
60 }
61 setMailDir(maildir);
62
63 QDir dir(mailDir());
64 QStringList files = dir.entryList(QStringList(QStringLiteral("*.[dDmM][bB][xX]")), QDir::Files, QDir::Name);
65 if (files.isEmpty()) {
66 filterInfo()->alert(i18n("No Outlook Express mailboxes found in directory %1.", mailDir()));
67 return;
68 }
69
70 totalFiles = files.count();
71 currentFile = 0;
72 count0x04 = 0;
73 count0x84 = 0;
74 parsedFolder = false;
75
76 filterInfo()->setOverall(0);
77
78 /** search the folderfile to recreate folder struct */
79
80 for (QStringList::Iterator mailFile = files.begin(); mailFile != files.end(); ++mailFile) {
81 if (*mailFile == QLatin1StringView("Folders.dbx")) {
82 filterInfo()->addInfoLogEntry(i18n("Import folder structure..."));
83 importMailBox(dir.filePath(*mailFile));
84 if (!folderStructure.isEmpty()) {
85 parsedFolder = true;
86 }
87 // remove file from QStringList::files, no longer needed
88 files.erase(mailFile);
89 currentIsFolderFile = false;
90 break;
91 }
92 }
93
94 int n = 0;
96 for (QStringList::ConstIterator mailFile = files.constBegin(); mailFile != end; ++mailFile) {
97 if (filterInfo()->shouldTerminate()) {
98 break;
99 }
100 importMailBox(dir.filePath(*mailFile));
101 filterInfo()->setOverall(100 * ++n / files.count());
102 }
103
104 filterInfo()->setOverall(100);
105 filterInfo()->setCurrent(100);
106 filterInfo()->addInfoLogEntry(i18n("Finished importing Outlook Express emails"));
107 if (filterInfo()->shouldTerminate()) {
108 filterInfo()->addInfoLogEntry(i18n("Finished import, canceled by user."));
109 }
110
111 qCDebug(MAILIMPORTER_LOG) << "total emails in current file:" << totalEmails;
112 qCDebug(MAILIMPORTER_LOG) << "0x84 Mails:" << count0x84;
113 qCDebug(MAILIMPORTER_LOG) << "0x04 Mails:" << count0x04;
114}
115
116void FilterOE::importMailBox(const QString &fileName)
117{
118 QFile mailfile(fileName);
119 QFileInfo mailfileinfo(fileName);
120 QString _nameOfFile = fileName;
121 _nameOfFile.remove(mailDir());
122 _nameOfFile.remove(QLatin1Char('/'));
123 filterInfo()->setFrom(mailfileinfo.fileName());
124
125 if (!mailfile.open(QIODevice::ReadOnly)) {
126 filterInfo()->addErrorLogEntry(i18n("Unable to open mailbox %1", fileName));
127 return;
128 }
129 QDataStream mailbox(&mailfile);
130 mailbox.setByteOrder(QDataStream::LittleEndian);
131
132 // Parse magic
133 quint32 sig_block1;
134 quint32 sig_block2;
135 mailbox >> sig_block1 >> sig_block2;
136 if (sig_block1 == OE4_SIG_1 && sig_block2 == OE4_SIG_2) {
137 folderName = QLatin1StringView("OE-Import/") + mailfileinfo.completeBaseName();
138 filterInfo()->addInfoLogEntry(i18n("Importing OE4 Mailbox %1", QStringLiteral("../") + _nameOfFile));
139 filterInfo()->setTo(folderName);
140 mbxImport(mailbox);
141 return;
142 } else {
143 quint32 sig_block3;
144 quint32 sig_block4;
145 mailbox >> sig_block3 >> sig_block4;
146 if (sig_block1 == OE5_SIG_1 && sig_block3 == OE5_SIG_3 && sig_block4 == OE5_SIG_4) {
147 if (sig_block2 == OE5_EMAIL_SIG_2) {
148 folderName = QLatin1StringView("OE-Import/") + mailfileinfo.completeBaseName();
149 if (parsedFolder) {
150 const QString _tmpFolder = getFolderName(_nameOfFile);
151 if (!_tmpFolder.isEmpty()) {
152 folderName = QLatin1StringView("OE-Import/") + _tmpFolder;
153 }
154 }
155 filterInfo()->addInfoLogEntry(i18n("Importing OE5+ Mailbox %1", QStringLiteral("../") + _nameOfFile));
156 filterInfo()->setTo(folderName);
157 dbxImport(mailbox);
158 return;
159 } else if (sig_block2 == OE5_FOLDER_SIG_2) {
160 if (!parsedFolder) {
161 filterInfo()->addInfoLogEntry(i18n("Importing OE5+ Folder file %1", QStringLiteral("../") + _nameOfFile));
162 currentIsFolderFile = true;
163 dbxImport(mailbox);
164 currentIsFolderFile = false;
165 }
166 return;
167 }
168 }
169 }
170}
171
172/* ------------------- MBX support ------------------- */
173
174void FilterOE::mbxImport(QDataStream &ds)
175{
176 quint32 msgCount;
177 quint32 lastMsgNum;
178 quint32 fileSize;
179
180 // Read the header
181 ds >> msgCount >> lastMsgNum >> fileSize;
182 ds.device()->seek(ds.device()->pos() + 64); // Skip 0's
183 qCDebug(MAILIMPORTER_LOG) << "This mailbox has" << msgCount << " messages";
184 if (msgCount == 0) {
185 return; // Don't import empty mailbox
186 }
187
188 quint32 msgMagic;
189 ds >> msgMagic; // Read first magic
190
191 while (!ds.atEnd()) {
192 quint32 msgNumber;
193 quint32 msgSize;
194 quint32 msgTextSize;
195 QTemporaryFile tmp;
196 tmp.open();
197 QDataStream dataStream(&tmp);
198 dataStream.setByteOrder(QDataStream::LittleEndian);
199
200 // Read the messages
201 ds >> msgNumber >> msgSize >> msgTextSize; // All seem to be lies...?
202 do {
203 ds >> msgMagic;
204 if (msgMagic != MBX_MAILMAGIC) {
205 dataStream << msgMagic;
206 } else {
207 break;
208 }
209 } while (!ds.atEnd());
210 tmp.flush();
211 if (!importMessage(folderName, tmp.fileName(), filterInfo()->removeDupMessage())) {
212 filterInfo()->addErrorLogEntry(i18n("Could not import %1", tmp.fileName()));
213 }
214
215 if (filterInfo()->shouldTerminate()) {
216 return;
217 }
218 }
219}
220
221/* ------------------- DBX support ------------------- */
222
223void FilterOE::dbxImport(QDataStream &ds)
224{
225 // Get item count &offset of index
226 quint32 itemCount;
227 quint32 indexPtr;
228 ds.device()->seek(0xc4);
229 ds >> itemCount;
230 ds.device()->seek(0xe4);
231 ds >> indexPtr;
232 qCDebug(MAILIMPORTER_LOG) << "Item count is" << itemCount << ", Index at" << indexPtr;
233
234 if (itemCount == 0) {
235 return; // Empty file
236 }
237 totalEmails = itemCount;
238 currentEmail = 0;
239 // Parse the indexes
240 ds.device()->seek(indexPtr);
241 dbxReadIndex(ds, indexPtr);
242}
243
244void FilterOE::dbxReadIndex(QDataStream &ds, int filePos)
245{
246 if (filterInfo()->shouldTerminate()) {
247 return;
248 }
249 quint32 self;
250 quint32 unknown;
251 quint32 nextIndexPtr;
252 quint32 parent;
253 quint32 indexCount;
254 quint8 unknown2;
255 quint8 ptrCount;
256 quint16 unknown3;
257 int wasAt = ds.device()->pos();
258 ds.device()->seek(filePos);
259
260 qCDebug(MAILIMPORTER_LOG) << "Reading index of file" << folderName;
261 ds >> self >> unknown >> nextIndexPtr >> parent >> unknown2 >> ptrCount >> unknown3 >> indexCount; // _dbx_tableindexstruct
262
263 qCDebug(MAILIMPORTER_LOG) << "This index has" << (int)ptrCount << " data pointers";
264 for (int count = 0; count < ptrCount; ++count) {
265 if (filterInfo()->shouldTerminate()) {
266 return;
267 }
268 quint32 dataIndexPtr;
269 quint32 anotherIndexPtr;
270 quint32 anotherIndexCount; // _dbx_indexstruct
271 ds >> dataIndexPtr >> anotherIndexPtr >> anotherIndexCount;
272
273 if (anotherIndexCount > 0) {
274 qCDebug(MAILIMPORTER_LOG) << "Recursing to another table @" << anotherIndexPtr;
275 dbxReadIndex(ds, anotherIndexPtr);
276 }
277 qCDebug(MAILIMPORTER_LOG) << "Data index @" << dataIndexPtr;
278 dbxReadDataBlock(ds, dataIndexPtr);
279 }
280
281 if (indexCount > 0) { // deal with nextTablePtr
282 qCDebug(MAILIMPORTER_LOG) << "Recurring to next table @" << nextIndexPtr;
283 dbxReadIndex(ds, nextIndexPtr);
284 }
285
286 ds.device()->seek(wasAt); // Restore file position to same as when function called
287}
288
289void FilterOE::dbxReadDataBlock(QDataStream &ds, int filePos)
290{
291 quint32 curOffset;
292 quint32 blockSize;
293 quint16 unknown;
294 quint8 count;
295 quint8 unknown2;
296 int wasAt = ds.device()->pos();
297
298 QString folderEntry[4];
299
300 ds.device()->seek(filePos);
301
302 ds >> curOffset >> blockSize >> unknown >> count >> unknown2; // _dbx_email_headerstruct
303 qCDebug(MAILIMPORTER_LOG) << "Data block has" << (int)count << " elements";
304
305 for (int c = 0; c < count; c++) {
306 if (filterInfo()->shouldTerminate()) {
307 return;
308 }
309 quint8 type; // _dbx_email_pointerstruct
310 quint32 value; // Actually 24 bit
311
312 ds >> type >> value;
313 value &= 0xffffff;
314 ds.device()->seek(ds.device()->pos() - 1); // We only wanted 3 bytes
315
316 if (!currentIsFolderFile) {
317 if (type == 0x84) { // It's an email!
318 qCDebug(MAILIMPORTER_LOG) << "**** Offset of emaildata (0x84)" << value << " ****";
319 dbxReadEmail(ds, value);
320 ++count0x84;
321 } else if (type == 0x04) {
322 int currentFilePos = ds.device()->pos();
323 ds.device()->seek(filePos + 12 + value + (count * 4));
324 quint32 newOFF;
325 ds >> newOFF;
326 qCDebug(MAILIMPORTER_LOG) << "**** Offset of emaildata (0x04)" << newOFF;
327 ds.device()->seek(currentFilePos);
328 dbxReadEmail(ds, newOFF);
329 ++count0x04;
330 }
331 } else {
332 // this is a folderfile
333 if (type == 0x02) {
334 // qCDebug(MAILIMPORTER_LOG) <<"**** FOLDER: descriptive name ****";
335 folderEntry[0] = parseFolderOEString(ds, filePos + 12 + value + (count * 4));
336 } else if (type == 0x03) {
337 // qCDebug(MAILIMPORTER_LOG) <<"**** FOLDER: filename ****";
338 folderEntry[1] = parseFolderOEString(ds, filePos + 12 + value + (count * 4));
339 } else if (type == 0x80) {
340 // qCDebug(MAILIMPORTER_LOG) <<"**** FOLDER: current ID ****";
341 folderEntry[2] = QString::number(value);
342 } else if (type == 0x81) {
343 // qCDebug(MAILIMPORTER_LOG) <<"**** FOLDER: parent ID ****";
344 folderEntry[3] = QString::number(value);
345 }
346 }
347 }
348 if (currentIsFolderFile) {
349 folderStructure.append(FolderStructure(folderEntry));
350 }
351 ds.device()->seek(wasAt); // Restore file position to same as when function called
352}
353
354void FilterOE::dbxReadEmail(QDataStream &ds, int filePos)
355{
356 if (filterInfo()->shouldTerminate()) {
357 return;
358 }
359 quint32 self;
360 quint32 nextAddressOffset;
361 quint32 nextAddress = 0;
362 quint16 blockSize;
363 quint8 intCount;
364 quint8 unknown;
365 QTemporaryFile tmp;
366 tmp.open();
367 bool _break = false;
368 int wasAt = ds.device()->pos();
369 ds.device()->seek(filePos);
370 QDataStream tempDs(&tmp);
371
372 do {
373 ds >> self >> nextAddressOffset >> blockSize >> intCount >> unknown >> nextAddress; // _dbx_block_hdrstruct
374 QByteArray blockBuffer(blockSize, '\0');
375 ds.readRawData(blockBuffer.data(), blockSize);
376 tempDs.writeRawData(blockBuffer.data(), blockSize);
377 // to detect incomplete mails or corrupted archives. See Bug #86119
378 if (ds.atEnd()) {
379 _break = true;
380 break;
381 }
382 ds.device()->seek(nextAddress);
383 } while (nextAddress != 0);
384 tmp.flush();
385
386 if (!_break) {
387 if (!importMessage(folderName, tmp.fileName(), filterInfo()->removeDupMessage())) {
388 filterInfo()->addErrorLogEntry(i18n("Could not import %1", tmp.fileName()));
389 }
390
391 currentEmail++;
392 int currentPercentage = (int)(((float)currentEmail / totalEmails) * 100);
393 filterInfo()->setCurrent(currentPercentage);
394 ds.device()->seek(wasAt);
395 }
396}
397
398/* ------------------- FolderFile support ------------------- */
399QString FilterOE::parseFolderOEString(QDataStream &ds, int filePos)
400{
401 char tmp;
402 QString returnString;
403 int wasAt = ds.device()->pos();
404 ds.device()->seek(filePos);
405
406 // read while != 0x00
407 while (!ds.device()->atEnd()) {
408 ds.device()->getChar(&tmp);
409 if (tmp != 0x00) {
410 returnString += QLatin1Char(tmp);
411 } else {
412 break;
413 }
414 }
415 ds.device()->seek(wasAt);
416 return returnString;
417}
418
419/** get the foldername for a given file ID from folderMatrix */
420QString FilterOE::getFolderName(const QString &filename)
421{
422 bool found = false;
423 bool foundFilename = false;
424 QString folder;
425 // we must do this because folder with more than one upper letter
426 // at start have maybe not a file named like the folder !!!
427 QString search = filename.toLower();
428
429 while (!found) {
430 for (FolderStructureIterator it = folderStructure.begin(); it != folderStructure.end(); ++it) {
431 FolderStructure tmp = *it;
432 if (foundFilename == false) {
433 QString _tmpFileName = tmp[1];
434 _tmpFileName = _tmpFileName.toLower();
435 if (_tmpFileName == search) {
436 folder.prepend(tmp[0] + QLatin1StringView("/"));
437 search = tmp[3];
438 foundFilename = true;
439 }
440 } else {
441 QString _currentID = tmp[2];
442 QString _parentID = tmp[3];
443 if (_currentID == search) {
444 if (_parentID.isEmpty()) { // this is the root of the folder
445 found = true;
446 break;
447 } else {
448 folder.prepend(tmp[0] + QLatin1StringView("/"));
449 search = tmp[3];
450 }
451 }
452 }
453 }
454 // need to break the while loop maybe in some cases
455 if ((foundFilename == false) && (folder.isEmpty())) {
456 return folder;
457 }
458 }
459 return folder;
460}
void importMails(const QString &maildir)
Definition filteroe.cpp:55
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
bool atEnd() const const
QIODevice * device() const const
int readRawData(char *s, int len)
QString homePath()
bool flush()
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, Options options)
virtual bool atEnd() const const
bool getChar(char *c)
virtual qint64 pos() const const
virtual bool seek(qint64 pos)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString toLower() const const
virtual QString fileName() const const override
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:08:02 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.