/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* dbx-importer.c
*
* Author: David Woodhouse <dwmw2@infradead.org>
*
* Copyright © 2010 Intel Corporation
*
* Evolution parts largely lifted from pst-import.c:
* Author: Chris Halls <chris.halls@credativ.co.uk>
* Bharath Acharya <abharath@novell.com>
* Copyright © 2006 Chris Halls
*
* Some DBX bits from libdbx:
* Author: David Smith <Dave.S@Earthcorp.Com>
* Copyright © 2001 David Smith
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This program 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define G_LOG_DOMAIN "eplugin-readdbx"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gstdio.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include <libecal/libecal.h>
#include <libebook/libebook.h>
#include <libedataserverui/libedataserverui.h>
#include <e-util/e-import.h>
#include <e-util/e-plugin.h>
#include <e-util/e-mktemp.h>
#include <shell/e-shell.h>
#include <shell/e-shell-window.h>
#include <shell/e-shell-view.h>
#include <libemail-utils/mail-mt.h>
#include <libemail-engine/mail-tools.h>
#include <mail/e-mail-backend.h>
#include <mail/em-folder-selection-button.h>
#include <mail/em-utils.h>
#define d(x)
#ifdef WIN32
#ifdef gmtime_r
#undef gmtime_r
#endif
#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
#endif
gboolean org_gnome_evolution_readdbx_supported
(EPlugin *epl,
EImportTarget *target);
GtkWidget * org_gnome_evolution_readdbx_getwidget
(EImport *ei,
EImportTarget *target,
EImportImporter *im);
void org_gnome_evolution_readdbx_import
(EImport *ei,
EImportTarget *target,
EImportImporter *im);
void org_gnome_evolution_readdbx_cancel
(EImport *ei,
EImportTarget *target,
EImportImporter *im);
gint e_plugin_lib_enable (EPlugin *ep,
gint enable);
/* em-folder-selection-button.h is private, even though other internal
* evo plugins use it!
* so declare the functions here
* TODO: sort out whether this should really be private
*/
typedef struct {
MailMsg base;
EImport *import;
EImportTarget *target;
GMutex status_lock;
gchar *status_what;
gint status_pc;
gint status_timeout_id;
GCancellable *cancellable;
guint32 *indices;
guint32 index_count;
gchar *uri;
gint dbx_fd;
CamelOperation *cancel;
CamelFolder *folder;
gchar *parent_uri;
gchar *folder_name;
gchar *folder_uri;
gint folder_count;
gint current_item;
} DbxImporter;
static guchar oe56_mbox_sig[16] = {
0xcf, 0xad, 0x12, 0xfe, 0xc5, 0xfd, 0x74, 0x6f,
0x66, 0xe3, 0xd1, 0x11, 0x9a, 0x4e, 0x00, 0xc0
};
static guchar oe56_flist_sig[16] = {
0xcf, 0xad, 0x12, 0xfe, 0xc6, 0xfd, 0x74, 0x6f,
0x66, 0xe3, 0xd1, 0x11, 0x9a, 0x4e, 0x00, 0xc0
};
static guchar oe4_mbox_sig[8] = {
0x4a, 0x4d, 0x46, 0x36, 0x03, 0x00, 0x01, 0x00
};
gboolean
org_gnome_evolution_readdbx_supported (EPlugin *epl,
EImportTarget *target)
{
gchar signature[16];
gboolean ret = FALSE;
gint fd, n;
EImportTargetURI *s;
gchar *filename;
if (target->type != E_IMPORT_TARGET_URI) {
return FALSE;
}
s = (EImportTargetURI *) target;
if (s->uri_src == NULL) {
return TRUE;
}
if (strncmp (s->uri_src, "file:///", strlen ("file:///")) != 0) {
return FALSE;
}
filename = g_filename_from_uri (s->uri_src, NULL, NULL);
fd = g_open (filename, O_RDONLY, 0);
g_free (filename);
if (fd != -1) {
n = read (fd, signature, sizeof (signature));
if (n == sizeof (signature)) {
if (!memcmp (signature, oe56_mbox_sig, sizeof (oe56_mbox_sig))) {
ret = TRUE;
} else if (!memcmp (signature, oe56_flist_sig, sizeof (oe56_flist_sig))) {
d (printf ("Found DBX folder list file\n"));
} else if (!memcmp (signature, oe4_mbox_sig, sizeof (oe4_mbox_sig))) {
d (printf ("Found OE4 DBX file\n"));
}
}
close (fd);
}
return ret;
}
static void
folder_selected (EMFolderSelectionButton *button,
EImportTargetURI *target)
{
g_free (target->uri_dest);
target->uri_dest = g_strdup (em_folder_selection_button_get_folder_uri (button));
}
GtkWidget *
org_gnome_evolution_readdbx_getwidget (EImport *ei,
EImportTarget *target,
EImportImporter *im)
{
EShell *shell;
EShellBackend *shell_backend;
EMailBackend *backend;
EMailSession *session;
GtkWidget *hbox, *w;
GtkLabel *label;
gchar *select_uri = NULL;
#if 1
GtkWindow *window;
/* preselect the folder selected in a mail view */
window = e_shell_get_active_window (e_shell_get_default ());
if (E_IS_SHELL_WINDOW (window)) {
EShellWindow *shell_window;
const gchar *view;
shell_window = E_SHELL_WINDOW (window);
view = e_shell_window_get_active_view (shell_window);
if (view && g_str_equal (view, "mail")) {
EShellView *shell_view;
EMFolderTree *folder_tree = NULL;
EShellSidebar *shell_sidebar;
shell_view = e_shell_window_get_shell_view (
shell_window, view);
shell_sidebar = e_shell_view_get_shell_sidebar (
shell_view);
g_object_get (
shell_sidebar, "folder-tree",
&folder_tree, NULL);
select_uri = em_folder_tree_get_selected_uri (
folder_tree);
}
}
#endif
shell = e_shell_get_default ();
shell_backend = e_shell_get_backend_by_name (shell, "mail");
backend = E_MAIL_BACKEND (shell_backend);
session = e_mail_backend_get_session (backend);
if (!select_uri) {
const gchar *local_inbox_uri;
local_inbox_uri =
e_mail_session_get_local_folder_uri (
session, E_MAIL_LOCAL_FOLDER_INBOX);
select_uri = g_strdup (local_inbox_uri);
}
hbox = gtk_hbox_new (FALSE, 0);
w = gtk_label_new_with_mnemonic (_("_Destination folder:"));
gtk_box_pack_start ((GtkBox *) hbox, w, FALSE, TRUE, 6);
label = GTK_LABEL (w);
w = em_folder_selection_button_new (
session, _("Select folder"),
_("Select folder to import into"));
gtk_label_set_mnemonic_widget (label, w);
em_folder_selection_button_set_folder_uri (
EM_FOLDER_SELECTION_BUTTON (w), select_uri);
folder_selected (
EM_FOLDER_SELECTION_BUTTON (w), (EImportTargetURI *) target);
g_signal_connect (
w, "selected",
G_CALLBACK (folder_selected), target);
gtk_box_pack_start ((GtkBox *) hbox, w, FALSE, TRUE, 6);
w = gtk_vbox_new (FALSE, 0);
gtk_box_pack_start ((GtkBox *) w, hbox, FALSE, FALSE, 0);
gtk_widget_show_all (w);
g_free (select_uri);
return w;
}
static gchar *
dbx_import_describe (DbxImporter *m,
gint complete)
{
return g_strdup (_("Importing Outlook Express data"));
}
/* Types taken from libdbx and fixed */
struct _dbx_tableindexstruct {
guint32 self;
guint32 unknown1;
guint32 anotherTablePtr;
guint32 parent;
gchar unknown2;
gchar ptrCount;
gchar reserve3;
gchar reserve4;
guint32 indexCount;
};
struct _dbx_indexstruct {
guint32 indexptr;
guint32 anotherTablePtr;
guint32 indexCount;
};
#define INDEX_POINTER 0xE4
#define ITEM_COUNT 0xC4
struct _dbx_email_headerstruct {
guint32 self;
guint32 size;
gushort u1;
guchar count;
guchar u2;
};
struct _dbx_block_hdrstruct {
guint32 self;
guint32 nextaddressoffset;
gushort blocksize;
guchar intcount;
guchar unknown1;
guint32 nextaddress;
};
static gint dbx_pread (gint fd, gpointer buf, guint32 count, guint32 offset)
{
if (lseek (fd, offset, SEEK_SET) != offset)
return -1;
return read (fd, buf, count);
}
static gboolean dbx_load_index_table (DbxImporter *m, guint32 pos, guint32 *index_ofs)
{
struct _dbx_tableindexstruct tindex;
struct _dbx_indexstruct index;
gint i;
d (printf ("Loading index table at 0x%x\n", pos));
if (dbx_pread (m->dbx_fd, &tindex, sizeof (tindex), pos) != sizeof (tindex)) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read table index from DBX file");
return FALSE;
}
tindex.anotherTablePtr = GUINT32_FROM_LE (tindex.anotherTablePtr);
tindex.self = GUINT32_FROM_LE (tindex.self);
tindex.indexCount = GUINT32_FROM_LE (tindex.indexCount);
if (tindex.self != pos) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Corrupt DBX file: Index table at 0x%x does not "
"point to itself", pos);
return FALSE;
}
d (
printf ("Index at %x: indexCount %x, anotherTablePtr %x\n",
pos, tindex.indexCount, tindex.anotherTablePtr));
if (tindex.indexCount > 0) {
if (!dbx_load_index_table (m, tindex.anotherTablePtr, index_ofs))
return FALSE;
}
d (printf ("Index at %x has ptrCount %d\n", pos, tindex.ptrCount));
pos += sizeof (tindex);
for (i = 0; i < tindex.ptrCount; i++) {
if (dbx_pread (m->dbx_fd, &index, sizeof (index), pos) != sizeof (index)) {
g_set_error (
&m->base.error,
CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read index entry from DBX file");
return FALSE;
}
index.indexptr = GUINT32_FROM_LE (index.indexptr);
index.anotherTablePtr = GUINT32_FROM_LE (index.anotherTablePtr);
index.indexCount = GUINT32_FROM_LE (index.indexCount);
if (*index_ofs == m->index_count) {
g_set_error (
&m->base.error,
CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Corrupt DBX file: Seems to contain more "
"than %d entries claimed in its header",
m->index_count);
return FALSE;
}
m->indices[(*index_ofs)++] = index.indexptr;
if (index.indexCount > 0) {
if (!dbx_load_index_table (m, index.anotherTablePtr, index_ofs))
return FALSE;
}
pos += sizeof (index);
}
return TRUE;
}
static gboolean dbx_load_indices (DbxImporter *m)
{
guint indexptr, itemcount;
guint32 index_ofs = 0;
if (dbx_pread (m->dbx_fd, &indexptr, 4, INDEX_POINTER) != 4) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read first index pointer from DBX file");
return FALSE;
}
if (dbx_pread (m->dbx_fd, &itemcount, 4, ITEM_COUNT) != 4) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read item count from DBX file");
return FALSE;
}
indexptr = GUINT32_FROM_LE (indexptr);
m->index_count = itemcount = GUINT32_FROM_LE (itemcount);
m->indices = g_malloc (itemcount * 4);
d (printf ("indexptr %x, itemcount %d\n", indexptr, itemcount));
if (indexptr && !dbx_load_index_table (m, indexptr, &index_ofs))
return FALSE;
d (printf ("Loaded %d of %d indices\n", index_ofs, m->index_count));
if (index_ofs < m->index_count) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Corrupt DBX file: Seems to contain fewer than %d "
"entries claimed in its header", m->index_count);
return FALSE;
}
return TRUE;
}
static gboolean
dbx_read_mail_body (DbxImporter *m,
guint32 offset,
gint bodyfd)
{
/* FIXME: We really ought to set up CamelStream that we can feed to the
* MIME parser, rather than using a temporary file */
struct _dbx_block_hdrstruct hdr;
guint32 buflen = 0x200;
guchar *buffer = g_malloc (buflen);
ftruncate (bodyfd, 0);
lseek (bodyfd, 0, SEEK_SET);
while (offset) {
d (printf ("Reading mail data chunk from %x\n", offset));
if (dbx_pread (m->dbx_fd, &hdr, sizeof (hdr), offset) != sizeof (hdr)) {
g_set_error (
&m->base.error,
CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read mail data block from "
"DBX file at offset %x", offset);
return FALSE;
}
hdr.self = GUINT32_FROM_LE (hdr.self);
hdr.blocksize = GUINT16_FROM_LE (hdr.blocksize);
hdr.nextaddress = GUINT32_FROM_LE (hdr.nextaddress);
if (hdr.self != offset) {
g_set_error (
&m->base.error,
CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Corrupt DBX file: Mail data block at "
"0x%x does not point to itself", offset);
return FALSE;
}
if (hdr.blocksize > buflen) {
g_free (buffer);
buflen = hdr.blocksize;
buffer = g_malloc (buflen);
}
if (dbx_pread (m->dbx_fd, buffer, hdr.blocksize,
offset + sizeof (hdr)) != hdr.blocksize) {
g_set_error (
&m->base.error,
CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read mail data from DBX file "
"at offset %lx",
(long)(offset + sizeof (hdr)));
return FALSE;
}
if (write (bodyfd, buffer, hdr.blocksize) != hdr.blocksize) {
g_set_error (
&m->base.error,
CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to write mail data to temporary file");
return FALSE;
}
offset = hdr.nextaddress;
}
return TRUE;
}
static gboolean
dbx_read_email (DbxImporter *m,
guint32 offset,
gint bodyfd,
gint *flags)
{
struct _dbx_email_headerstruct hdr;
guchar *buffer;
guint32 dataptr = 0;
gint i;
if (dbx_pread (m->dbx_fd, &hdr, sizeof (hdr), offset) != sizeof (hdr)) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read mail header from DBX file at offset %x",
offset);
return FALSE;
}
hdr.self = GUINT32_FROM_LE (hdr.self);
hdr.size = GUINT32_FROM_LE (hdr.size);
if (hdr.self != offset) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Corrupt DBX file: Mail header at 0x%x does not "
"point to itself", offset);
return FALSE;
}
buffer = g_malloc (hdr.size);
offset += sizeof (hdr);
if (dbx_pread (m->dbx_fd, buffer, hdr.size, offset) != hdr.size) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to read mail data block from DBX file "
"at offset %x", offset);
g_free (buffer);
return FALSE;
}
for (i = 0; i < hdr.count; i++) {
guchar type = buffer[i *4];
gint val;
val = buffer[i *4 + 1] +
(buffer[i *4 + 2] << 8) +
(buffer[i *4 + 3] << 16);
switch (type) {
case 0x01:
*flags = buffer[hdr.count*4 + val];
d (printf ("Got type 0x01 flags %02x\n", *flags));
break;
case 0x81:
*flags = val;
d (printf ("Got type 0x81 flags %02x\n", *flags));
break;
case 0x04:
dataptr = GUINT32_FROM_LE (*(guint32 *)(buffer + hdr.count *4 + val));
d (printf ("Got type 0x04 data pointer %x\n", dataptr));
break;
case 0x84:
dataptr = val;
d (printf ("Got type 0x84 data pointer %x\n", dataptr));
break;
default:
/* We don't care about anything else */
d (printf ("Ignoring type %02x datum\n", type));
break;
}
}
g_free (buffer);
if (!dataptr)
return FALSE;
return dbx_read_mail_body (m, dataptr, bodyfd);
}
static void
dbx_import_file (DbxImporter *m)
{
EShell *shell;
EShellBackend *shell_backend;
EMailSession *session;
GCancellable *cancellable;
gchar *filename;
CamelFolder *folder;
gint tmpfile;
gint i;
gint missing = 0;
m->status_what = NULL;
filename = g_filename_from_uri (
((EImportTargetURI *) m->target)->uri_src, NULL, NULL);
/* Destination folder, was set in our widget */
m->parent_uri = g_strdup (((EImportTargetURI *) m->target)->uri_dest);
cancellable = m->base.cancellable;
/* XXX Dig up the EMailSession from the default EShell.
* Since the EImport framework doesn't allow for user
* data, I don't see how else to get to it. */
shell = e_shell_get_default ();
shell_backend = e_shell_get_backend_by_name (shell, "mail");
session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend));
camel_operation_push_message (NULL, _("Importing '%s'"), filename);
folder = e_mail_session_uri_to_folder_sync (
session, m->parent_uri, CAMEL_STORE_FOLDER_CREATE,
cancellable, &m->base.error);
if (!folder)
return;
d (printf ("importing to %s\n", camel_folder_get_full_name (folder)));
camel_folder_freeze (folder);
filename = g_filename_from_uri (
((EImportTargetURI *) m->target)->uri_src, NULL, NULL);
m->dbx_fd = g_open (filename, O_RDONLY, 0);
g_free (filename);
if (m->dbx_fd == -1) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to open import file");
goto out;
}
if (!dbx_load_indices (m))
goto out;
tmpfile = e_mkstemp ("dbx-import-XXXXXX");
if (tmpfile == -1) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"Failed to create temporary file for import");
goto out;
}
for (i = 0; i < m->index_count; i++) {
CamelMessageInfo *info;
CamelMimeMessage *msg;
CamelMimeParser *mp;
gint dbx_flags = 0;
gint flags = 0;
gboolean success;
camel_operation_progress (NULL, 100 * i / m->index_count);
camel_operation_progress (cancellable, 100 * i / m->index_count);
if (!dbx_read_email (m, m->indices[i], tmpfile, &dbx_flags)) {
d (
printf ("Cannot read email index %d at %x\n",
i, m->indices[i]));
if (m->base.error != NULL)
goto out;
missing++;
continue;
}
if (dbx_flags & 0x40)
flags |= CAMEL_MESSAGE_DELETED;
if (dbx_flags & 0x80)
flags |= CAMEL_MESSAGE_SEEN;
if (dbx_flags & 0x80000)
flags |= CAMEL_MESSAGE_ANSWERED;
mp = camel_mime_parser_new ();
lseek (tmpfile, 0, SEEK_SET);
camel_mime_parser_init_with_fd (mp, tmpfile);
msg = camel_mime_message_new ();
if (!camel_mime_part_construct_from_parser_sync (
(CamelMimePart *) msg, mp, NULL, NULL)) {
/* set exception? */
g_object_unref (msg);
g_object_unref (mp);
break;
}
info = camel_message_info_new (NULL);
camel_message_info_set_flags (info, flags, ~0);
success = camel_folder_append_message_sync (
folder, msg, info, NULL,
cancellable, &m->base.error);
camel_message_info_free (info);
g_object_unref (msg);
if (!success) {
g_object_unref (mp);
break;
}
}
out:
if (m->dbx_fd != -1)
close (m->dbx_fd);
if (m->indices)
g_free (m->indices);
/* FIXME Not passing GCancellable or GError here. */
camel_folder_synchronize_sync (folder, FALSE, NULL, NULL);
camel_folder_thaw (folder);
g_object_unref (folder);
if (missing && m->base.error == NULL) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
"%d messages imported correctly; %d message "
"bodies were not present in the DBX file",
m->index_count - missing, missing);
}
camel_operation_pop_message (NULL);
}
static void
dbx_import_import (DbxImporter *m,
GCancellable *cancellable,
GError **error)
{
dbx_import_file (m);
}
static void
dbx_import_imported (DbxImporter *m)
{
e_import_complete (m->target->import, (EImportTarget *) m->target);
}
static void
dbx_import_free (DbxImporter *m)
{
g_free (m->status_what);
g_mutex_clear (&m->status_lock);
g_source_remove (m->status_timeout_id);
m->status_timeout_id = 0;
g_free (m->folder_name);
g_free (m->folder_uri);
g_free (m->parent_uri);
g_object_unref (m->import);
}
static MailMsgInfo dbx_import_info = {
sizeof (DbxImporter),
(MailMsgDescFunc) dbx_import_describe,
(MailMsgExecFunc) dbx_import_import,
(MailMsgDoneFunc) dbx_import_imported,
(MailMsgFreeFunc) dbx_import_free,
};
static gboolean
dbx_status_timeout (gpointer data)
{
DbxImporter *importer = data;
gint pc;
gchar *what;
if (importer->status_what) {
g_mutex_lock (&importer->status_lock);
what = importer->status_what;
importer->status_what = NULL;
pc = importer->status_pc;
g_mutex_unlock (&importer->status_lock);
e_import_status (
importer->target->import,
(EImportTarget *) importer->target, what, pc);
}
return TRUE;
}
static void
dbx_status (CamelOperation *op,
const gchar *what,
gint pc,
gpointer data)
{
DbxImporter *importer = data;
g_mutex_lock (&importer->status_lock);
g_free (importer->status_what);
importer->status_what = g_strdup (what);
importer->status_pc = pc;
g_mutex_unlock (&importer->status_lock);
}
/* Start the main import operation */
void
org_gnome_evolution_readdbx_import (EImport *ei,
EImportTarget *target,
EImportImporter *im)
{
DbxImporter *m;
m = mail_msg_new (&dbx_import_info);
g_datalist_set_data (&target->data, "dbx-msg", m);
m->import = ei;
g_object_ref (m->import);
m->target = target;
m->parent_uri = NULL;
m->folder_name = NULL;
m->folder_uri = NULL;
m->status_timeout_id = g_timeout_add (100, dbx_status_timeout, m);
/*m->status_timeout_id = NULL;*/
g_mutex_init (&m->status_lock);
m->cancellable = camel_operation_new ();
g_signal_connect (
m->cancellable, "status",
G_CALLBACK (dbx_status), m);
mail_msg_unordered_push (m);
}
void
org_gnome_evolution_readdbx_cancel (EImport *ei,
EImportTarget *target,
EImportImporter *im)
{
DbxImporter *m = g_datalist_get_data (&target->data, "dbx-msg");
if (m) {
g_cancellable_cancel (m->cancellable);
}
}
gint
e_plugin_lib_enable (EPlugin *ep,
gint enable)
{
return 0;
}