diff options
Diffstat (limited to 'camel/providers/mbox/camel-mbox-summary.c')
-rw-r--r-- | camel/providers/mbox/camel-mbox-summary.c | 1556 |
1 files changed, 1197 insertions, 359 deletions
diff --git a/camel/providers/mbox/camel-mbox-summary.c b/camel/providers/mbox/camel-mbox-summary.c index e15bb13b03..11f1c5779a 100644 --- a/camel/providers/mbox/camel-mbox-summary.c +++ b/camel/providers/mbox/camel-mbox-summary.c @@ -1,420 +1,1258 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - -/* - * Author : Bertrand Guiheneuf <bertrand@helixcode.com> +/* + * Copyright (C) 2000 Helix Code Inc. * - * Copyright (C) 1999 - 2000 Helix Code . - - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. + * Authors: Michael Zucchi <notzed@helixcode.com> + * + * This program 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 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 General Public License for more details. + * 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 Library General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ - -#include <config.h> - -#include "camel-exception.h" -#include "camel-mbox-folder.h" -#include "camel-mbox-summary.h" -#include "md5-utils.h" - - -#include <sys/stat.h> -#include <unistd.h> #include <sys/types.h> +#include <sys/stat.h> #include <fcntl.h> -#include <dirent.h> +#include <unistd.h> +#include <sys/uio.h> + #include <stdio.h> #include <string.h> + +#include <gtk/gtk.h> + +#include <camel/camel-mime-parser.h> +#include <camel/camel-mime-filter.h> +#include <camel/camel-mime-filter-basic.h> +#include <camel/camel-mime-filter-charset.h> +#include <camel/camel-mime-filter-index.h> + +#include <camel/camel-mime-utils.h> + +#include "camel-mbox-summary.h" + #include <errno.h> +#include <ctype.h> #include <netinet/in.h> -static CamelFolderSummaryClass *parent_class = NULL; +#define d(x) -static int count_messages (CamelFolderSummary *summary); -static int count_subfolders (CamelFolderSummary *summary); -static GPtrArray *get_subfolder_info (CamelFolderSummary *summary, - int first, int count); -static GPtrArray *get_message_info (CamelFolderSummary *summary, - int first, int count); -static void finalize (GtkObject *object); +#define CAMEL_MBOX_SUMMARY_VERSION 2 -static void -camel_mbox_summary_class_init (CamelMboxSummaryClass *camel_mbox_summary_class) +static int safe_write(int fd, char *buffer, size_t towrite); +static void camel_mbox_summary_add(CamelMboxSummary *s, CamelMboxMessageInfo *info); + +/* + Disk file format? + + message uid +message-block + date: + date received? + + subject: (unicode encoded) + from: (unicode encoded) + to: (unicode) + + content-block + +content-block + content-type: ; params; + content-id: + content-description: + content-transfer-encoding: + message-start: + header-size: + body-size: + + message-block + multipart-block + + */ + +/* pah, i dont care, its almost no code and it works, dont need a glist */ +struct _node { + struct _node *next; +}; + +static struct _node * +my_list_append(struct _node **list, struct _node *n) +{ + struct _node *ln = (struct _node *)list; + while (ln->next) + ln = ln->next; + n->next = 0; + ln->next = n; + return n; +} + +static int +my_list_size(struct _node **list) +{ + int len = 0; + struct _node *ln = (struct _node *)list; + while (ln->next) { + ln = ln->next; + len++; + } + return len; +} + +/* low-level io functions */ +static int +encode_int (FILE *out, gint32 value) +{ + int i; + + for (i=28;i>0;i-=7) { + if (value >= (1<<i)) { + unsigned int c = (value>>i) & 0x7f; + if (fputc(c, out) == -1) + return -1; + } + } + return fputc(value | 0x80, out); +} + +static gint32 +decode_int (FILE *in) { - GtkObjectClass *gtk_object_class = - GTK_OBJECT_CLASS (camel_mbox_summary_class); - CamelFolderSummaryClass *camel_folder_summary_class = - CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_class); + gint32 value=0, v; - parent_class = gtk_type_class (camel_folder_summary_get_type ()); + /* until we get the last byte, keep decoding 7 bits at a time */ + while ( ((v = fgetc(in)) & 0x80) == 0 && v!=EOF) { + value |= v; + value <<= 7; + } + value |= (v&0x7f); + return value; +} - /* virtual method override */ - camel_folder_summary_class->count_messages = count_messages; - camel_folder_summary_class->count_subfolders = count_subfolders; - camel_folder_summary_class->get_subfolder_info = get_subfolder_info; - camel_folder_summary_class->get_message_info = get_message_info; +static int +encode_fixed_int (FILE *out, gint32 value) +{ + guint32 save; - gtk_object_class->finalize = finalize; + save = htonl(value); + return fwrite(&save, sizeof(save), 1, out); } +static gint32 +decode_fixed_int (FILE *out) +{ + guint32 save; -GtkType -camel_mbox_summary_get_type (void) + if (fread(&save, sizeof(save), 1, out) != -1) { + return ntohl(save); + } else { + return -1; + } +} + +/* should be sorted, for binary search */ +/* This is a tokenisation mechanism for strings written to the + summary - to save space. + This list can have at most 31 words. */ +static char * tokens[] = { + "7bit", + "8bit", + "alternative", + "application", + "base64", + "boundary", + "charset", + "filename", + "html", + "image", + "iso-8859-1", + "iso-8859-8", + "message", + "mixed", + "multipart", + "name", + "octet-stream", + "parallel", + "plain", + "quoted-printable", + "rfc822", + "text", + "us-ascii", /* 23 words */ +}; + +#define tokens_len (sizeof(tokens)/sizeof(tokens[0])) + +/* baiscally ... + 0 = null + 1-tokens_len == tokens[id-1] + >=32 string, length = n-32 +*/ + +static int +encode_string (FILE *out, char *str) { - static GtkType camel_mbox_summary_type = 0; + if (str == NULL) { + return encode_int(out, 0); + } else { + int len = strlen(str); + int i, token=-1; + + if (len <= 16) { + char lower[32]; + + for (i=0;i<len;i++) + lower[i] = tolower(str[i]); + lower[i] = 0; + for (i=0;i<tokens_len;i++) { + if (!strcmp(tokens[i], lower)) { + token = i; + break; + } + } + } + if (token != -1) { + return encode_int(out, token+1); + } else { + if (encode_int(out, len+32) == -1) + return -1; + return fwrite(str, len, 1, out); + } + } + return 0; +} - if (!camel_mbox_summary_type) { - GtkTypeInfo camel_mbox_summary_info = - { - "CamelMboxSummary", - sizeof (CamelMboxSummary), - sizeof (CamelMboxSummaryClass), - (GtkClassInitFunc) camel_mbox_summary_class_init, - (GtkObjectInitFunc) NULL, - /* reserved_1 */ NULL, - /* reserved_2 */ NULL, - (GtkClassInitFunc) NULL, - }; +static char * +decode_string (FILE *in) +{ + char *ret; + int len; + + len = decode_int(in); - camel_mbox_summary_type = gtk_type_unique (camel_folder_summary_get_type (), &camel_mbox_summary_info); + if (len<32) { + if (len <= 0) { + ret = NULL; + } else if (len<= tokens_len) { + ret = g_strdup(tokens[len-1]); + } else { + g_warning("Invalid token encountered: %d", len); + ret = NULL; + } + } else if (len > 10240) { + g_warning("Got broken string header length: %d bytes", len); + ret = NULL; + } else { + len -= 32; + ret = g_malloc(len+1); + if (fread(ret, len, 1, in) == -1) { + g_free(ret); + return NULL; + } + ret[len]=0; } - return camel_mbox_summary_type; + return ret; } + + +/* allocation functions */ + static void -finalize (GtkObject *object) +body_part_dump(CamelMboxMessageContentInfo *bs, int depth) { - CamelMboxSummary *summary = CAMEL_MBOX_SUMMARY (object); - CamelMboxSummaryInformation *info; - int i; + CamelMboxMessageContentInfo *c; + char *prefix; - for (i = 0; i < summary->message_info->len; i++) { - info = &(((CamelMboxSummaryInformation *)summary->message_info->data)[i]); - g_free (info->headers.subject); - g_free (info->headers.sender); - g_free (info->headers.to); - g_free (info->headers.sent_date); - g_free (info->headers.received_date); - g_free (info->headers.uid); + if (bs == NULL) + return; + + prefix = alloca(depth*2+1); + memset(prefix, ' ', depth*2); + prefix[depth*2]=0; + printf("%scontent-range: %d %d %d\n", prefix, (int)bs->pos, (int)bs->bodypos, (int)bs->endpos); + printf("%scontent-type: %s/%s\n", prefix, bs->info.type?bs->info.type->type:"?", bs->info.type?bs->info.type->subtype:"?"); + printf("%scontent-id: %s\n", prefix, bs->info.id); + printf("%scontent-description: %s\n", prefix, bs->info.description); + printf("%scontent-transfer-encoding: %s\n", prefix, bs->info.encoding); + c = (CamelMboxMessageContentInfo *)bs->info.childs; + while (c) { + printf("%s -- \n", prefix); + body_part_dump(c, depth+1); + c = (CamelMboxMessageContentInfo *)c->info.next; } - g_array_free (summary->message_info, TRUE); +} - GTK_OBJECT_CLASS (parent_class)->finalize (object); -} +static void +message_struct_dump(CamelMboxMessageInfo *ms) +{ + char *tmp; -static int -count_messages (CamelFolderSummary *summary) + if (ms == NULL) { + printf("Empty message?\n"); + return; + } + + printf("Subject: %s\n", ms->info.subject); + printf("From: %s\n", ms->info.from); + printf("To: %s\n", ms->info.to); + tmp = header_format_date(ms->info.date_sent, 0); + printf("Date: %s\n", tmp); + g_free(tmp); + tmp = header_format_date(ms->info.date_received, 0); + printf("Date-Received: %s\n", tmp); + g_free(tmp); + printf("UID: %08x-%04x\n", atoi(ms->info.uid), ms->info.flags); + printf(" -- content ->\n"); + body_part_dump((CamelMboxMessageContentInfo *)ms->info.content, 1); +} + +static CamelMboxMessageContentInfo * +body_part_new(CamelMimeParser *mp, CamelMboxMessageContentInfo *parent, int start, int body) { - return CAMEL_MBOX_SUMMARY (summary)->nb_message; + CamelMboxMessageContentInfo *bs; + + bs = g_malloc0(sizeof(*bs)); + + bs->info.parent = (CamelMessageContentInfo *)parent; + + bs->info.type = camel_mime_parser_content_type(mp); + header_content_type_ref(bs->info.type); + + bs->info.id = header_msgid_decode(camel_mime_parser_header(mp, "content-id", NULL)); + bs->info.description = header_decode_string(camel_mime_parser_header(mp, "content-description", NULL)); + bs->info.encoding = header_content_encoding_decode(camel_mime_parser_header(mp, "content-transfer-encoding", NULL)); + + /* not sure what to set here? */ + bs->pos = start; + bs->bodypos = body; + bs->endpos = -1; + + if (parent) + my_list_append((struct _node **)&parent->info.childs, (struct _node *)bs); + + return bs; +} + +static CamelMboxMessageInfo * +message_struct_new(CamelMimeParser *mp, CamelMboxMessageContentInfo *parent, int start, int body, off_t xev_offset) +{ + CamelMboxMessageInfo *ms; + + ms = g_malloc0(sizeof(*ms)); + + /* FIXME: what about cc, sender vs from? */ + ms->info.subject = g_strdup(camel_mime_parser_header(mp, "subject", NULL)); + ms->info.from = g_strdup(camel_mime_parser_header(mp, "from", NULL)); + ms->info.to = g_strdup(camel_mime_parser_header(mp, "to", NULL)); + + ms->info.date_sent = header_decode_date(camel_mime_parser_header(mp, "date", NULL), NULL); + ms->info.date_received = 0; + + ms->info.content = (CamelMessageContentInfo *)body_part_new(mp, parent, start, body); + ms->xev_offset = xev_offset; + return ms; +} + +static void +body_part_free(CamelMboxMessageContentInfo *bs) +{ + CamelMboxMessageContentInfo *c, *cn; + + c = (CamelMboxMessageContentInfo *)bs->info.childs; + while (c) { + cn = (CamelMboxMessageContentInfo *)c->info.next; + body_part_free(c); + c = cn; + } + g_free(bs->info.id); + g_free(bs->info.description); + g_free(bs->info.encoding); + header_content_type_unref(bs->info.type); + g_free(bs); +} + +static void +message_struct_free(CamelMboxMessageInfo *ms) +{ + g_free(ms->info.subject); + g_free(ms->info.to); + g_free(ms->info.from); + body_part_free((CamelMboxMessageContentInfo *)ms->info.content); + g_free(ms); +} + + +/* IO functions */ +static CamelMboxMessageContentInfo * +body_part_load(FILE *in) +{ + CamelMboxMessageContentInfo *bs = NULL, *c; + struct _header_content_type *ct; + char *type; + char *subtype; + int i, count; + + d(printf("got content-block\n")); + bs = g_malloc0(sizeof(*bs)); + bs->pos = decode_int(in); + bs->bodypos = bs->pos + decode_int(in); + bs->endpos = bs->pos + decode_int(in); + + /* do content type */ + d(printf("got content-type\n")); + type = decode_string(in); + subtype = decode_string(in); + + ct = header_content_type_new(type, subtype); + bs->info.type = ct; + count = decode_int(in); + d(printf("getting %d params\n", count)); + for (i=0;i<count;i++) { + char *name = decode_string(in); + char *value = decode_string(in); + + d(printf(" %s = \"%s\"\n", name, value)); + + header_content_type_set_param(ct, name, value); + /* FIXME: do this so we dont have to double alloc/free */ + g_free(name); + g_free(value); + } + + d(printf("got content-id\n")); + bs->info.id = decode_string(in); + d(printf("got content-description\n")); + bs->info.description = decode_string(in); + d(printf("got content-encoding\n")); + bs->info.encoding = decode_string(in); + + count = decode_int(in); + d(printf("got children, %d\n", count)); + for (i=0;i<count;i++) { + c = body_part_load(in); + if (c) { + my_list_append((struct _node **)&bs->info.childs, (struct _node *)c); + c->info.parent = (CamelMessageContentInfo *)bs; + } else { + printf("Cannot load child\n"); + } + } + + return bs; } static int -count_subfolders (CamelFolderSummary *summary) +body_part_save(FILE *out, CamelMboxMessageContentInfo *bs) { - /* XXX */ - g_warning ("CamelMboxSummary::count_subfolders not implemented"); + CamelMboxMessageContentInfo *c, *cn; + struct _header_content_type *ct; + struct _header_param *hp; + + encode_int(out, bs->pos); + encode_int(out, bs->bodypos - bs->pos); + encode_int(out, bs->endpos - bs->pos); + + ct = bs->info.type; + if (ct) { + encode_string(out, ct->type); + encode_string(out, ct->subtype); + encode_int(out, my_list_size((struct _node **)&ct->params)); + hp = ct->params; + while (hp) { + encode_string(out, hp->name); + encode_string(out, hp->value); + hp = hp->next; + } + } else { + encode_string(out, NULL); + encode_string(out, NULL); + encode_int(out, 0); + } + encode_string(out, bs->info.id); + encode_string(out, bs->info.description); + encode_string(out, bs->info.encoding); + + encode_int(out, my_list_size((struct _node **)&bs->info.childs)); + + c = (CamelMboxMessageContentInfo *)bs->info.childs; + while (c) { + cn = (CamelMboxMessageContentInfo *)c->info.next; + body_part_save(out, c); + c = cn; + } + return 0; } -static GPtrArray * -get_subfolder_info (CamelFolderSummary *summary, int first, int count) +static CamelMboxMessageInfo * +message_struct_load(FILE *in) { - /* XXX */ - g_warning ("CamelMboxSummary::count_subfolders not implemented"); + CamelMboxMessageInfo *ms; + + ms = g_malloc0(sizeof(*ms)); + + ms->info.uid = g_strdup_printf("%u", decode_int(in)); + ms->info.flags = decode_int(in); + ms->info.date_sent = decode_int(in); + ms->info.date_received = decode_int(in); + ms->xev_offset = decode_int(in); + ms->info.subject = decode_string(in); + ms->info.from = decode_string(in); + ms->info.to = decode_string(in); + ms->info.content = (CamelMessageContentInfo *)body_part_load(in); + + return ms; +} + +static int +message_struct_save(FILE *out, CamelMboxMessageInfo *ms) +{ + encode_int(out, strtoul(ms->info.uid, NULL, 10)); + encode_int(out, ms->info.flags); + encode_int(out, ms->info.date_sent); + encode_int(out, ms->info.date_received); + encode_int(out, ms->xev_offset); + encode_string(out, ms->info.subject); + encode_string(out, ms->info.from); + encode_string(out, ms->info.to); + body_part_save(out, (CamelMboxMessageContentInfo *)ms->info.content); + return 0; } -static GPtrArray * -get_message_info (CamelFolderSummary *summary, int first, int count) +static unsigned int +header_evolution_decode(const char *in, unsigned int *uid, unsigned int *flags) { - CamelMboxSummary *mbox_summary = CAMEL_MBOX_SUMMARY (summary); - CamelMboxSummaryInformation *info; - GPtrArray *arr; + char *header; + if (in + && (header = header_decode_token(&in))) { + if (strlen(header) == strlen("00000000-0000") + && sscanf(header, "%08x-%04x", uid, flags) == 2) { + g_free(header); + return *uid; + } + g_free(header); + } - /* XXX bounds check */ + return ~0; +} + +static int +safe_write(int fd, char *buffer, size_t towrite) +{ + size_t donelen; + size_t len; - arr = g_ptr_array_new (); - for (; count; count--) { - info = &((CamelMboxSummaryInformation *)mbox_summary->message_info->data)[first++]; - g_ptr_array_add (arr, info); + donelen = 0; + while (donelen < towrite) { + len = write(fd, buffer + donelen, towrite - donelen); + if (len == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + donelen += len; } + return donelen; +} - return arr; -} - -/** - * camel_mbox_summary_save: - * @summary: - * @filename: - * @ex: - * - * save the summary into a file - **/ -void -camel_mbox_summary_save (CamelMboxSummary *summary, const gchar *filename, - CamelException *ex) -{ - CamelMboxSummaryInformation *msg_info; - guint cur_msg; - guint field_length; - gint fd; - gint write_result; /* XXX use this */ - guint32 data; - - fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR); - if (fd == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, - "could not create the mbox summary " - "file\n\t%s\nFull error is : %s\n", - filename, - strerror (errno)); - return; +static int +header_write(int fd, struct _header_raw *header, unsigned int uid, unsigned int flags) +{ + struct iovec iv[3]; + int outlen = 0; + + iv[1].iov_base = ":"; + iv[1].iov_len = 1; + + while (header) { + if (strcasecmp(header->name, "x-evolution")) { + int len; + + iv[0].iov_base = header->name; + iv[0].iov_len = strlen(header->name); + iv[2].iov_base = header->value; + iv[2].iov_len = strlen(header->value); + + do { + len = writev(fd, iv, 3); + } while (len == -1 && errno == EINTR); + + if (len == -1) + return -1; + outlen += len; + } + header = header->next; } - /* We write the file out in network byte order, not because - * that makes sense, but because it's easy. - */ - - data = htonl (CAMEL_MBOX_SUMMARY_VERSION); - write (fd, &data, sizeof (data)); - - data = htonl (summary->nb_message); - write (fd, &data, sizeof (data)); - data = htonl (summary->next_uid); - write (fd, &data, sizeof (data)); - data = htonl (summary->mbox_file_size); - write (fd, &data, sizeof (data)); - data = htonl (summary->mbox_modtime); - write (fd, &data, sizeof (data)); - - for (cur_msg = 0; cur_msg < summary->nb_message; cur_msg++) { - msg_info = (CamelMboxSummaryInformation *) - (summary->message_info->data) + cur_msg; - - /* Write meta-info. */ - data = htonl (msg_info->position); - write (fd, &data, sizeof (data)); - data = htonl (msg_info->size); - write (fd, &data, sizeof (data)); - data = htonl (msg_info->x_evolution_offset); - write (fd, &data, sizeof (data)); - data = htonl (msg_info->uid); - write (fd, &data, sizeof (data)); - write (fd, &msg_info->status, 1); - - /* Write subject. */ - if (msg_info->headers.subject) - field_length = strlen (msg_info->headers.subject); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.subject) - write (fd, msg_info->headers.subject, field_length); - - /* Write sender. */ - if (msg_info->headers.sender) - field_length = strlen (msg_info->headers.sender); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.sender) - write (fd, msg_info->headers.sender, field_length); - - /* Write to. */ - if (msg_info->headers.to) - field_length = strlen (msg_info->headers.to); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.to) - write (fd, msg_info->headers.to, field_length); - - /* Write sent date. */ - if (msg_info->headers.sent_date) - field_length = strlen (msg_info->headers.sent_date); - else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.sent_date) - write (fd, msg_info->headers.sent_date, field_length); - - /* Write received date. */ - if (msg_info->headers.received_date) - field_length = strlen (msg_info->headers.received_date); + return outlen; +} + +/* returns -1 on error, else number of bytes written */ +int +camel_mbox_summary_copy_block(int fromfd, int tofd, off_t readpos, size_t bytes) +{ + char buffer[4096]; + int written = 0; + off_t pos, newpos; + + pos = lseek(fromfd, 0, SEEK_CUR); + if (pos == -1) + return -1; + + newpos = lseek(fromfd, readpos, SEEK_SET); + if (newpos == -1 || newpos != readpos) + goto error; + + d(printf("oldpos = %d; copying %d from %d\n", (int)pos, (int)bytes, (int)readpos)); + + while (bytes>0) { + int toread, towrite, donelen; + + toread = bytes; + if (bytes>4096) + toread = 4096; else - field_length = 0; - data = htonl (field_length); - write (fd, &data, sizeof (data)); - if (msg_info->headers.received_date) - write (fd, msg_info->headers.received_date, field_length); + toread = bytes; + reread: + towrite = read(fromfd, buffer, toread); + if (towrite == -1) { + if (errno == EINTR || errno == EAGAIN) + goto reread; + goto error; + } + + /* check for 'end of file' */ + if (towrite == 0) + break; + + if ( (donelen = safe_write(tofd, buffer, towrite)) == -1) + goto error; + + written += donelen; + bytes -= donelen; + } + + d(printf("written %d bytes\n", written)); + + newpos = lseek(fromfd, pos, SEEK_SET); + if (newpos == -1 || newpos != pos); + return -1; + + return written; + +error: + lseek(fromfd, pos, SEEK_SET); + return -1; +} + +#define SAVEIT + +static int index_folder(CamelMboxSummary *s, int startoffset) +{ + CamelMimeParser *mp; + int fd; + int fdout; + int state; + + int toplevel = FALSE; + const char *xev; + char *data; + int datalen; + + int enc_id=-1; + int chr_id=-1; + int idx_id=-1; + struct _header_content_type *ct; + int doindex=FALSE; + CamelMimeFilterCharset *mfc = NULL; + CamelMimeFilterIndex *mfi = NULL; + CamelMimeFilterBasic *mf64 = NULL, *mfqp = NULL; + + CamelMboxMessageContentInfo *body = NULL, *parent = NULL; + CamelMboxMessageInfo *message = NULL; + + int from_end = 0; /* start of message */ + int from = 0; /* start of headers */ + int last_write = 0; /* last written position */ + int eof; + int write_offset = 0; /* how much does the dest differ from the source pos */ + int old_offset = 0; + + guint32 newuid; + off_t xevoffset = -1; + + char *tmpname; + + printf("indexing %s (%s) from %d\n", s->folder_path, s->summary_path, startoffset); + + fd = open(s->folder_path, O_RDONLY); + if (fd==-1) { + perror("Can't open folder"); + return -1; } - close (fd); -} - - - -/** - * camel_mbox_summary_load: - * @filename: - * @ex: - * - * load the summary from a file - * - * Return value: - **/ -CamelMboxSummary * -camel_mbox_summary_load (const gchar *filename, CamelException *ex) -{ - CamelMboxSummaryInformation *msg_info; - guint cur_msg; - guint field_length; - gint fd; - CamelMboxSummary *summary; - gint read_result; - guint32 data; - - fd = open (filename, O_RDONLY); - if (fd == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, - "could not open the mbox summary file\n" - "\t%s\nFull error is : %s\n", - filename, strerror (errno)); - return NULL; + tmpname = g_strdup_printf("%s.tmp", s->folder_path); + + fdout = open(tmpname, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (fdout==-1) { + perror("Can't open output"); + g_free(tmpname); + return -1; } - /* Verify version number. */ - read (fd, &data, sizeof(data)); - data = ntohl (data); - - if (data != CAMEL_MBOX_SUMMARY_VERSION) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_SUMMARY_INVALID, - "This folder summary was written by " - "%s version of this software.", - data < CAMEL_MBOX_SUMMARY_VERSION ? - "an older" : "a newer"); - return NULL; + mp = camel_mime_parser_new(); + camel_mime_parser_init_with_fd(mp, fd); + camel_mime_parser_scan_from(mp, TRUE); + + /* FIXME: cleaner fail code */ + if (startoffset > 0) { + if (camel_mime_parser_seek(mp, startoffset, SEEK_SET) != startoffset) { + g_free(tmpname); + gtk_object_unref((GtkObject *)mp); + return -1; + } } - summary = CAMEL_MBOX_SUMMARY (gtk_object_new (camel_mbox_summary_get_type (), NULL)); - - read (fd, &data, sizeof(data)); - summary->nb_message = ntohl (data); - read (fd, &data, sizeof(data)); - summary->next_uid = ntohl (data); - read (fd, &data, sizeof(data)); - summary->mbox_file_size = ntohl (data); - read (fd, &data, sizeof(data)); - summary->mbox_modtime = ntohl (data); - - summary->message_info = - g_array_new (FALSE, FALSE, - sizeof (CamelMboxSummaryInformation)); - g_array_set_size (summary->message_info, summary->nb_message); - - for (cur_msg = 0; cur_msg < summary->nb_message; cur_msg++) { - msg_info = (CamelMboxSummaryInformation *) - (summary->message_info->data) + cur_msg; - - /* Read the meta-info. */ - read (fd, &data, sizeof(data)); - msg_info->position = ntohl (data); - read (fd, &data, sizeof(data)); - msg_info->size = ntohl (data); - read (fd, &data, sizeof(data)); - msg_info->x_evolution_offset = ntohl (data); - read (fd, &data, sizeof(data)); - msg_info->uid = ntohl (data); - msg_info->headers.uid = g_strdup_printf ("%d", msg_info->uid); - read (fd, &msg_info->status, 1); - - /* Read the subject. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.subject = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.subject, field_length); - } else - msg_info->headers.subject = NULL; - - /* Read the sender. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.sender = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.sender, field_length); - } else - msg_info->headers.sender = NULL; - - /* Read the "to" field. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.to = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.to, field_length); - } else - msg_info->headers.to = NULL; - - /* Read the sent date field. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.sent_date = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.sent_date, field_length); - } else - msg_info->headers.sent_date = NULL; - - /* Read the received date field. */ - read (fd, &field_length, sizeof (field_length)); - field_length = ntohl (field_length); - if (field_length > 0) { - msg_info->headers.received_date = - g_new0 (gchar, field_length + 1); - read (fd, msg_info->headers.received_date, - field_length); - } else - msg_info->headers.received_date = NULL; - } - - close (fd); - return summary; -} - - -/** - * camel_mbox_summary_append_entries: - * @summary: - * @entries: - * - * append an entry to a summary - **/ -void -camel_mbox_summary_append_entries (CamelMboxSummary *summary, GArray *entries) -{ - - summary->message_info = g_array_append_vals (summary->message_info, - entries->data, - entries->len); + mfi = camel_mime_filter_index_new_ibex(s->index); + + while ( (state = camel_mime_parser_step(mp, &data, &datalen)) != HSCAN_EOF ) { + switch(state) { + case HSCAN_FROM: /* starting a new message content */ + /* save the current position */ + d(printf("from = %d\n", (int)camel_mime_parser_tell(mp))); + toplevel = FALSE; + from = camel_mime_parser_tell(mp); + break; + + case HSCAN_FROM_END: + d(printf("from-end = %d\n", (int)camel_mime_parser_tell(mp))); + d(printf("message from %d to %d\n", from_end, (int)camel_mime_parser_tell(mp))); + from_end = camel_mime_parser_tell(mp); + break; + + case HSCAN_MESSAGE: + case HSCAN_MULTIPART: + case HSCAN_HEADER: /* starting a new header */ + newuid=~0; + if (!toplevel) { + char name[32]; + unsigned int olduid, oldflags; + int headerlen; + int docopy = FALSE; + + /* check for X-Evolution header ... if its there, nothing to do (skip content) */ + xev = camel_mime_parser_header(mp, "x-evolution", &xevoffset); + if (xev) { + d(printf("An x-evolution header exists at: %d = %s\n", xevoffset + write_offset, xev)); + xevoffset = xevoffset + write_offset; + if (header_evolution_decode(xev, &olduid, &oldflags) != ~0) { + d(printf(" uid = %d = %x\n", olduid, olduid)); + newuid = olduid; +#if 0 + while (camel_mime_parser_step(mp, &data, &datalen) != HSCAN_FROM_END) + ; + break; +#endif + } else { + printf("Invalid xev header? I need to write out a new one ...\n"); + } + } + + toplevel = TRUE; + + /* assign a new uid for this message */ + if (newuid == ~0) { + newuid = s->nextuid++; + docopy = TRUE; + } else { + /* make sure we account for this uid when assigning uid's */ + /* this really needs a pre-scan pass ... *sigh* */ + camel_mbox_summary_set_uid(s, newuid); + } + + /* setup index name for this uid */ + sprintf(name, "%x", newuid); + camel_mime_filter_index_set_name(mfi, name); + /* remove all references to this name from the index */ + if (s->index) + ibex_unindex(s->index, name); + + d(printf("Message content starts at %d\n", camel_mime_parser_tell(mp))); + + if (docopy) { + /* now, copy over bits of mbox from last write, and insert the X-Evolution header (at the top of headers) */ + /* if we already have a valid x-evolution header, use that, dont need to copy */ + camel_mbox_summary_copy_block(fd, fdout, last_write, from-last_write); + last_write = from; + + headerlen = header_write(fdout, camel_mime_parser_headers_raw(mp), newuid, 0); + sprintf(name, "X-Evolution: %08x-%04x\n\n", newuid, 0); + safe_write(fdout, name, strlen(name)); + d(printf("new X-Evolution at %d\n", headerlen + from + write_offset)); + xevoffset = headerlen + from + write_offset; + old_offset = write_offset; + + write_offset += (headerlen - (camel_mime_parser_tell(mp)-from)) + strlen(name); + last_write = camel_mime_parser_tell(mp); + } + } else { + old_offset = write_offset; + } + + /* we only care about the rest for actual content parts */ + /* TODO: Cleanup, this is a huge mess */ + if (state != HSCAN_HEADER) { + if (message == NULL) { + message = message_struct_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset, xevoffset); + parent = (CamelMboxMessageContentInfo *)message->info.content; + if (newuid != ~0) { + message->info.uid = g_strdup_printf("%u", newuid); + } else { + g_warning("This shouldn't happen?"); + } + } else { + parent = body_part_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset); + } + break; + } + + if (message == NULL) { + message = message_struct_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset, xevoffset); + body = (CamelMboxMessageContentInfo *)message->info.content; + if (newuid != ~0) { + message->info.uid = g_strdup_printf("%u", newuid); + } else { + g_warning("This shouldn't happen?"); + } + } else { + body = body_part_new(mp, parent, camel_mime_parser_tell_start_headers(mp)+old_offset, camel_mime_parser_tell(mp)+write_offset); + } + + /* check headers for types that we can index */ + ct = camel_mime_parser_content_type(mp); + if (header_content_type_is(ct, "text", "*")) { + char *encoding; + const char *charset; + + /* TODO: The filters should all be cached, so they aren't recreated between + messages/message parts */ + encoding = header_content_encoding_decode(camel_mime_parser_header(mp, "content-transfer-encoding", NULL)); + if (encoding) { + if (!strcasecmp(encoding, "base64")) { + d(printf("Adding decoding filter for base64\n")); + if (mf64 == NULL) + mf64 = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_BASE64_DEC); + enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mf64); + } else if (!strcasecmp(encoding, "quoted-printable")) { + d(printf("Adding decoding filter for quoted-printable\n")); + if (mfqp == NULL) + mfqp = camel_mime_filter_basic_new_type(CAMEL_MIME_FILTER_BASIC_QP_DEC); + enc_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfqp); + } + g_free(encoding); + } + + charset = header_content_type_param(ct, "charset"); + if (charset!=NULL + && !(strcasecmp(charset, "us-ascii")==0 + || strcasecmp(charset, "utf-8")==0)) { + d(printf("Adding conversion filter from %s to utf-8\n", charset)); + if (mfc == NULL) + mfc = camel_mime_filter_charset_new_convert(charset, "utf-8"); + if (mfc) { + chr_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfc); + } else { + g_warning("Cannot convert '%s' to 'utf-8', message display may be corrupt", charset); + } + } + + doindex = TRUE; + + /* and this filter actually does the indexing */ + idx_id = camel_mime_parser_filter_add(mp, (CamelMimeFilter *)mfi); + } else { + doindex = FALSE; + } + break; + + /* fixme, this needs thought *sigh* */ + case HSCAN_MESSAGE_END: + case HSCAN_MULTIPART_END: + if (parent) { + parent->endpos = camel_mime_parser_tell(mp)+write_offset; + if (parent->info.parent == NULL) { + camel_mbox_summary_add(s, message); + message = NULL; + parent = NULL; + } else { + parent = (CamelMboxMessageContentInfo *)parent->info.parent; + } + } + break; + + case HSCAN_BODY: + if (doindex) { + d(printf("Got content to index:\n%.*s", datalen, data)); + } + break; + + case HSCAN_BODY_END: + if (body) { + body->endpos = camel_mime_parser_tell(mp)+write_offset; + if (body->info.parent == NULL) { + camel_mbox_summary_add(s, message); + message = NULL; + } + } + + d(printf("end of content, removing decoders\n")); + if (enc_id != -1) { + camel_mime_parser_filter_remove(mp, enc_id); + enc_id = -1; + } + if (chr_id != -1) { + camel_mime_parser_filter_remove(mp, chr_id); + chr_id = -1; + } + if (idx_id != -1) { + camel_mime_parser_filter_remove(mp, idx_id); + idx_id = -1; + } + break; + } + } + + /* did we actually write anything out? Then rename and be done with it. */ + if (last_write>0) { + eof = camel_mime_parser_tell(mp); + camel_mbox_summary_copy_block(fd, fdout, last_write, eof-last_write); + + if (close(fdout) == -1) { + perror("Could not close output file"); + unlink(tmpname); + } else { + printf("renaming %s to %s\n", tmpname, s->folder_path); + if (rename(tmpname, s->folder_path) == -1) { + perror("Error renaming file"); + unlink(tmpname); + } + } + } else { + /* no, then dont bother touching the inbox */ + printf("No written changes to mbox, removing tmp file\n"); + close(fdout); + unlink(tmpname); + } + + close(fd); + + if (mf64) gtk_object_unref((GtkObject *)mf64); + if (mfqp) gtk_object_unref((GtkObject *)mfqp); + if (mfc) gtk_object_unref((GtkObject *)mfc); + if (mfi) gtk_object_unref((GtkObject *)mfi); + + /* force an index sync? */ + if (s->index) { + ibex_write(s->index); + } + + gtk_object_unref((GtkObject *)mp); + + /* and finally ... update the summary sync info */ + { + struct stat st; + + if (stat(s->folder_path, &st) == 0) { + s->time = st.st_mtime; + s->size = st.st_size; + } + } + + g_free(tmpname); + + return 0; +} + +CamelMboxSummary *camel_mbox_summary_new(const char *summary, const char *folder, ibex *index) +{ + CamelMboxSummary *s; + + s = g_malloc0(sizeof(*s)); + + s->dirty = TRUE; + s->folder_path = g_strdup(folder); + s->summary_path = g_strdup(summary); + /* FIXME: refcount index? */ + s->index = index; + + s->messages = g_ptr_array_new(); + s->message_uid = g_hash_table_new(g_str_hash, g_str_equal); + + /* always force an update */ + s->time = 0; + s->size = 0; + + s->nextuid = 1; + + /* TODO: force an initial load right now? */ + + return s; +} + +void camel_mbox_summary_unref(CamelMboxSummary *s) +{ + g_warning("Unimplemented function, mbox_summary_unref"); +} + +/* check that the summary is uptodate, TRUE means it is uptodate */ +int camel_mbox_summary_check(CamelMboxSummary *s) +{ + struct stat st; + + /* no folder at all? */ + if (stat(s->folder_path, &st) != 0) + return FALSE; + + return (st.st_size == s->size) && (st.st_mtime == s->time); +} + +static void camel_mbox_summary_add(CamelMboxSummary *s, CamelMboxMessageInfo *info) +{ + if (info->info.uid == NULL) { + info->info.uid = g_strdup_printf("%u", s->nextuid++); + } + if (g_hash_table_lookup(s->message_uid, info->info.uid)) { + g_error("Trying to insert message with clashing uid's"); + } + d(printf("adding %s\n", info->info.uid)); + g_ptr_array_add(s->messages, info); + g_hash_table_insert(s->message_uid, info->info.uid, info); + s->dirty = TRUE; +} + +static int summary_header_read(FILE *fp, guint32 *version, time_t *time, size_t *size, guint32 *nextuid) +{ + fseek(fp, 0, SEEK_SET); + *version = decode_fixed_int(fp); + *time = decode_fixed_int(fp); + *size = decode_fixed_int(fp); + *nextuid = decode_fixed_int(fp); + return ferror(fp); +} + +static void +summary_clear(CamelMboxSummary *s) +{ + int i; + + for (i=0;i<s->messages->len;i++) { + message_struct_free(g_ptr_array_index(s->messages, i)); + } + g_ptr_array_free(s->messages, TRUE); + g_hash_table_destroy(s->message_uid); + + s->messages = g_ptr_array_new(); + s->message_uid = g_hash_table_new(g_str_hash, g_str_equal); +} + +int camel_mbox_summary_load(CamelMboxSummary *s) +{ + struct stat st; + FILE *fp; + int i, total; + CamelMboxMessageInfo *info; + + summary_clear(s); + + if ((fp = fopen(s->summary_path, "r")) == NULL) { + g_warning("Loading non-existant summary, generating summary for folder: %s: %s", s->summary_path, strerror(errno)); + index_folder(s, 0); + camel_mbox_summary_save(s); + } else { + guint32 version, nextuid; + time_t time; + size_t size; + + if (stat(s->folder_path, &st) != 0) { + g_warning("Uh, no folder anyway, aborting"); + fclose(fp); + return -1; + } + + if (summary_header_read(fp, &version, &time, &size, &nextuid) != 0 + || version != CAMEL_MBOX_SUMMARY_VERSION) { + g_warning("Summary missing or version mismatch, reloading summary"); + fclose(fp); + index_folder(s, 0); + camel_mbox_summary_save(s); + return 0; + } + + s->nextuid = MAX(s->nextuid, nextuid); + s->time = time; + s->size = size; + total = decode_fixed_int(fp); + + if (time != st.st_mtime || size != st.st_size) { + /* if its grown, then just index the new stuff, and load the rest from the summary */ + if (size < st.st_size) { + g_warning("Indexing/summarizing from start position: %d", size); + + d(printf("loading %d items from summary file\n", total)); + for (i=0;i<total;i++) { + info = message_struct_load(fp); + if (info) { + camel_mbox_summary_add(s, info); + } else { + break; + } + } + fclose(fp); + s->dirty = FALSE; + index_folder(s, size); /* if it adds any, it'll dirtify it */ + camel_mbox_summary_save(s); + } else { + g_warning("Folder changed/smaller, reindexing everything"); + index_folder(s, 0); + camel_mbox_summary_save(s); + fclose(fp); + } + return 0; + } + + printf("loading %d items from summary file\n", total); + for (i=0;i<total;i++) { + info = message_struct_load(fp); + if (info) { + camel_mbox_summary_add(s, info); + } else { + break; + } + } + fclose(fp); + s->dirty = FALSE; + } + return 0; +} + +static int summary_header_write(FILE *fp, CamelMboxSummary *s) +{ + fseek(fp, 0, SEEK_SET); + encode_fixed_int(fp, CAMEL_MBOX_SUMMARY_VERSION); + encode_fixed_int(fp, s->time); + /* if we're dirty, then dont *really* save it ... */ + if (s->dirty) + encode_fixed_int(fp, 0); + else + encode_fixed_int(fp, s->size); + encode_fixed_int(fp, s->nextuid); + fflush(fp); + return ferror(fp); +} + +static int summary_header_save(CamelMboxSummary *s) +{ + int fd; + FILE *fp; + + fd = open(s->summary_path, O_WRONLY|O_CREAT, 0600); + if (fd == -1) + return -1; + fp = fdopen(fd, "w"); + if (fp == NULL) + return -1; + + summary_header_write(fp, s); + return fclose(fp); +} + +int camel_mbox_summary_save(CamelMboxSummary *s) +{ + int i, fd; + FILE *fp; + + printf("saving summary? %s\n", s->summary_path); + + /* FIXME: error checking */ + if (s->dirty) { + printf("yes\n"); + fd = open(s->summary_path, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (fd == -1) + return -1; + fp = fdopen(fd, "w"); + if (fp == NULL) + return -1; + + s->dirty = FALSE; + + summary_header_write(fp, s); + encode_fixed_int(fp, s->messages->len); + + printf("message count = %d\n", s->messages->len); + + for (i=0;i<s->messages->len;i++) { + message_struct_save(fp, g_ptr_array_index(s->messages, i)); + } + fclose(fp); + } else { + printf("no\n"); + } + return 0; +} + +CamelMboxMessageInfo *camel_mbox_summary_uid(CamelMboxSummary *s, const char *uid) +{ + return g_hash_table_lookup(s->message_uid, uid); +} + +CamelMboxMessageInfo *camel_mbox_summary_index(CamelMboxSummary *s, int index) +{ + return g_ptr_array_index(s->messages, index); +} + +int camel_mbox_summary_message_count(CamelMboxSummary *s) +{ + return s->messages->len; +} + +guint32 camel_mbox_summary_next_uid(CamelMboxSummary *s) +{ + guint32 uid = s->nextuid++; + + summary_header_save(s); + return uid; +} + +guint32 camel_mbox_summary_set_uid(CamelMboxSummary *s, guint32 uid) +{ + if (s->nextuid < uid) { + s->nextuid = uid; + summary_header_save(s); + } + return s->nextuid; +} + +#if 0 +int main(int argc, char **argv) +{ + if (argc<2) { + printf("usage: %s mbox\n", argv[0]); + return 1; + } + + gtk_init(&argc, &argv); + + index_folder(argv[1]); + + return 0; } +#endif |