# HG changeset patch # User Timo Sirainen # Date 1043074371 -7200 # Node ID 8028c4dcf38f4827fca5dc9939222afc26d6ae09 # Parent cbf096fbb9f02f73f71634030a945a7334ab3cb8 mail-storage.h interface changes, affects pretty much everything. FETCH, SEARCH, SORT and THREAD handling were pretty much moved from lib-storage/ to imap/ so adding non-index storages would be much easier now. Also POP3 server can now be easily implemented with lib-storage. Not too well tested, and at least one major problem: partial fetching is _slow_. diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/Makefile.am --- a/src/imap/Makefile.am Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/Makefile.am Mon Jan 20 16:52:51 2003 +0200 @@ -56,13 +56,23 @@ client.c \ commands.c \ commands-util.c \ + imap-fetch.c \ + imap-fetch-body-section.c \ + imap-search.c \ + imap-sort.c \ + imap-thread.c \ mail-storage-callbacks.c \ main.c \ rawlog.c + noinst_HEADERS = \ client.h \ commands.h \ commands-util.h \ common.h \ + imap-fetch.h \ + imap-search.h \ + imap-sort.h \ + imap-thread.h \ rawlog.h diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/cmd-append.c --- a/src/imap/cmd-append.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/cmd-append.c Mon Jan 20 16:52:51 2003 +0200 @@ -76,9 +76,8 @@ { struct imap_arg_list *flags_list; struct mailbox *box; - enum mail_flags flags; + struct mail_full_flags flags; time_t internal_date; - const char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT]; const char *mailbox, *internal_date_str; uoff_t msg_size; int failed, timezone_offset; @@ -97,13 +96,10 @@ if (flags_list != NULL) { if (!client_parse_mail_flags(client, flags_list->args, - flags_list->size, - &flags, custom_flags)) + &flags)) return TRUE; } else { - if (!client_parse_mail_flags(client, NULL, 0, - &flags, custom_flags)) - return TRUE; + memset(&flags, 0, sizeof(flags)); } if (internal_date_str == NULL) { @@ -131,8 +127,7 @@ o_stream_flush(client->output); /* save the mail */ - failed = !box->save(box, flags, custom_flags, - internal_date, timezone_offset, + failed = !box->save(box, &flags, internal_date, timezone_offset, client->input, msg_size); box->close(box); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/cmd-fetch.c --- a/src/imap/cmd-fetch.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/cmd-fetch.c Mon Jan 20 16:52:51 2003 +0200 @@ -2,6 +2,7 @@ #include "common.h" #include "commands.h" +#include "imap-fetch.h" /* Parse next digits in string into integer. Returns FALSE if the integer becomes too big and wraps. */ @@ -23,23 +24,96 @@ return TRUE; } -/* BODY[] and BODY.PEEK[] items. item points to next character after '[' */ -static int parse_body_section(struct client *client, const char *item, - struct mail_fetch_data *data, int peek) +static int check_header_section(const char *section) +{ + /* HEADER, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */ + if (*section == '\0') + return TRUE; + + if (strncmp(section, ".FIELDS", 7) != 0) + return FALSE; + + section += 7; + if (strncmp(section, ".NOT", 4) == 0) + section += 4; + + while (*section == ' ') section++; + if (*section++ != '(') + return FALSE; + + while (*section != '\0' && *section != ')') { + if (*section == '(') + return FALSE; + section++; + } + + if (*section++ != ')') + return FALSE; + + if (*section != '\0') + return FALSE; + return TRUE; +} + +static int check_section(struct client *client, const char *section, + enum mail_fetch_field *fetch_data) { - struct mail_fetch_body_data *body; + if (*section == '\0') { + *fetch_data |= MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY; + return TRUE; + } + + if (strcmp(section, "TEXT") == 0) { + *fetch_data |= MAIL_FETCH_STREAM_BODY; + return TRUE; + } + + if (strncmp(section, "HEADER", 6) == 0) { + *fetch_data |= MAIL_FETCH_STREAM_HEADER; + if (check_header_section(section+6)) + return TRUE; + } else if (*section >= '0' && *section <= '9') { + *fetch_data |= MAIL_FETCH_STREAM_BODY | + MAIL_FETCH_MESSAGE_PARTS; + + while ((*section >= '0' && *section <= '9') || + *section == '.') section++; + + if (*section == '\0') + return TRUE; + if (strcmp(section, "MIME") == 0 || + strcmp(section, "TEXT") == 0) + return TRUE; + + if (strncmp(section, "HEADER", 6) == 0 && + check_header_section(section+6)) + return TRUE; + } + + client_send_tagline(client, t_strconcat( + "BAD Invalid BODY[] section: ", section, NULL)); + return FALSE; +} + +/* BODY[] and BODY.PEEK[] items. item points to next character after '[' */ +static int parse_body_section(struct client *client, const char *item, int peek, + enum mail_fetch_field *fetch_data, + struct imap_fetch_body_data ***bodies) +{ + /* @UNSAFE */ + struct imap_fetch_body_data *body; uoff_t num; - const char *section; char *p; - body = t_new(struct mail_fetch_body_data, 1); + body = t_new(struct imap_fetch_body_data, 1); body->peek = peek; p = t_strdup_noconst(item); /* read section */ body->section = p; - for (section = p; *p != ']'; p++) { + for (; *p != ']'; p++) { if (*p == '\0') { client_send_tagline(client, t_strconcat( "BAD Missing ']' with ", item, NULL)); @@ -48,6 +122,9 @@ } *p++ = '\0'; + if (!check_section(client, body->section, fetch_data)) + return FALSE; + /* */ body->skip = 0; body->max_size = (uoff_t)-1; @@ -89,14 +166,15 @@ } } - body->next = data->body_sections; - data->body_sections = body; - + **bodies = body; + *bodies = &body->next; return TRUE; } static int parse_arg(struct client *client, struct imap_arg *arg, - struct mail_fetch_data *data) + enum mail_fetch_field *fetch_data, + enum imap_fetch_field *imap_data, + struct imap_fetch_body_data ***bodies) { char *item; @@ -111,10 +189,10 @@ switch (*item) { case 'A': if (strcmp(item, "ALL") == 0) { - data->flags = TRUE; - data->internaldate = TRUE; - data->rfc822_size = TRUE; - data->envelope = TRUE; + *fetch_data |= MAIL_FETCH_FLAGS | + MAIL_FETCH_RECEIVED_DATE | + MAIL_FETCH_SIZE | + MAIL_FETCH_IMAP_ENVELOPE; } else item = NULL; break; @@ -128,46 +206,48 @@ if (*item == '\0') { /* BODY */ - data->body = TRUE; + *fetch_data |= MAIL_FETCH_IMAP_BODY; } else if (*item == '[') { /* BODY[...] */ - if (!parse_body_section(client, item+1, data, FALSE)) + if (!parse_body_section(client, item+1, FALSE, + fetch_data, bodies)) return FALSE; } else if (strncmp(item, ".PEEK[", 6) == 0) { /* BODY.PEEK[..] */ - if (!parse_body_section(client, item+6, data, TRUE)) + if (!parse_body_section(client, item+6, TRUE, + fetch_data, bodies)) return FALSE; } else if (strcmp(item, "STRUCTURE") == 0) { /* BODYSTRUCTURE */ - data->bodystructure = TRUE; + *fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE; } else item = NULL; break; case 'E': if (strcmp(item, "ENVELOPE") == 0) - data->envelope = TRUE; + *fetch_data |= MAIL_FETCH_IMAP_ENVELOPE; else item = NULL; break; case 'F': if (strcmp(item, "FLAGS") == 0) - data->flags = TRUE; + *fetch_data |= MAIL_FETCH_FLAGS; else if (strcmp(item, "FAST") == 0) { - data->flags = TRUE; - data->internaldate = TRUE; - data->rfc822_size = TRUE; + *fetch_data |= MAIL_FETCH_FLAGS | + MAIL_FETCH_RECEIVED_DATE | + MAIL_FETCH_SIZE; } else if (strcmp(item, "FULL") == 0) { - data->flags = TRUE; - data->internaldate = TRUE; - data->rfc822_size = TRUE; - data->envelope = TRUE; - data->body = TRUE; + *fetch_data |= MAIL_FETCH_FLAGS | + MAIL_FETCH_RECEIVED_DATE | + MAIL_FETCH_SIZE | + MAIL_FETCH_IMAP_ENVELOPE | + MAIL_FETCH_IMAP_BODY; } else item = NULL; break; case 'I': if (strcmp(item, "INTERNALDATE") == 0) - data->internaldate = TRUE; + *fetch_data |= MAIL_FETCH_RECEIVED_DATE; else item = NULL; break; @@ -181,7 +261,9 @@ if (*item == '\0') { /* RFC822 */ - data->rfc822 = TRUE; + *fetch_data |= MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY; + *imap_data |= IMAP_FETCH_RFC822; break; } @@ -192,18 +274,20 @@ } item++; - if (strcmp(item, "HEADER") == 0) - data->rfc822_header = TRUE; - else if (strcmp(item, "SIZE") == 0) - data->rfc822_size = TRUE; - else if (strcmp(item, "TEXT") == 0) - data->rfc822_text = TRUE; + if (strcmp(item, "HEADER") == 0) { + *fetch_data |= MAIL_FETCH_STREAM_HEADER; + *imap_data |= IMAP_FETCH_RFC822_HEADER; + } else if (strcmp(item, "TEXT") == 0) { + *fetch_data |= MAIL_FETCH_STREAM_BODY; + *imap_data |= IMAP_FETCH_RFC822_TEXT; + } else if (strcmp(item, "SIZE") == 0) + *fetch_data |= MAIL_FETCH_SIZE; else item = NULL; break; case 'U': if (strcmp(item, "UID") == 0) - data->uid = TRUE; + *imap_data |= IMAP_FETCH_UID; else item = NULL; break; @@ -225,9 +309,11 @@ int cmd_fetch(struct client *client) { struct imap_arg *args, *listargs; - struct mail_fetch_data data; + enum mail_fetch_field fetch_data; + enum imap_fetch_field imap_data; + struct imap_fetch_body_data *bodies, **bodies_p; const char *messageset; - int all_found; + int ret; if (!client_read_args(client, 2, 0, &args)) return FALSE; @@ -243,33 +329,29 @@ } /* parse items argument */ - memset(&data, 0, sizeof(struct mail_fetch_data)); + fetch_data = 0; imap_data = 0; bodies = NULL; bodies_p = &bodies; if (args[1].type == IMAP_ARG_ATOM) { - if (!parse_arg(client, &args[1], &data)) + if (!parse_arg(client, &args[1], &fetch_data, + &imap_data, &bodies_p)) return TRUE; } else { listargs = IMAP_ARG_LIST(&args[1])->args; while (listargs->type != IMAP_ARG_EOL) { - if (!parse_arg(client, listargs, &data)) + if (!parse_arg(client, listargs, &fetch_data, + &imap_data, &bodies_p)) return TRUE; listargs++; } } - data.messageset = messageset; - data.uidset = client->cmd_uid; - if (data.uidset) - data.uid = TRUE; - - /* fetch it */ - if (client->mailbox->fetch(client->mailbox, &data, - client->output, &all_found)) { + ret = imap_fetch(client, fetch_data, imap_data, + bodies, messageset, client->cmd_uid); + if (ret >= 0) { /* NOTE: syncing isn't allowed here */ client_sync_without_expunges(client); - client_send_tagline(client, all_found ? "OK Fetch completed." : - "NO Some of the requested messages " - "no longer exist."); + client_send_tagline(client, ret > 0 ? "OK Fetch completed." : + "NO Some of the requested messages no longer exist."); } else { client_send_storage_error(client); } diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/cmd-search.c --- a/src/imap/cmd-search.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/cmd-search.c Mon Jan 20 16:52:51 2003 +0200 @@ -1,8 +1,50 @@ /* Copyright (C) 2002 Timo Sirainen */ #include "common.h" +#include "ostream.h" +#include "str.h" #include "commands.h" -#include "mail-search.h" +#include "imap-search.h" + +#define STRBUF_SIZE 1024 + +static int imap_search(struct client *client, const char *charset, + struct mail_search_arg *sargs) +{ + struct mail_search_context *ctx; + const struct mail *mail; + string_t *str; + int ret, uid, first = TRUE; + + str = t_str_new(STRBUF_SIZE); + uid = client->cmd_uid; + + ctx = client->mailbox->search_init(client->mailbox, charset, sargs, + NULL, 0, NULL); + if (ctx == NULL) + return FALSE; + + str_append(str, "* SEARCH"); + while ((mail = client->mailbox->search_next(ctx)) != NULL) { + if (str_len(str) >= STRBUF_SIZE-MAX_INT_STRLEN) { + /* flush */ + o_stream_send(client->output, + str_data(str), str_len(str)); + str_truncate(str, 0); + first = FALSE; + } + + str_printfa(str, " %u", uid ? mail->uid : mail->seq); + } + + ret = client->mailbox->search_deinit(ctx); + + if (!first || ret) { + str_append(str, "\r\n"); + o_stream_send(client->output, str_data(str), str_len(str)); + } + return ret; +} int cmd_search(struct client *client) { @@ -44,23 +86,19 @@ pool = pool_alloconly_create("mail_search_args", 2048); - sargs = mail_search_args_build(pool, args, &error); + sargs = imap_search_args_build(pool, args, &error); if (sargs == NULL) { /* error in search arguments */ client_send_tagline(client, t_strconcat("NO ", error, NULL)); + } else if (imap_search(client, charset, sargs)) { + /* NOTE: syncing is allowed when returning UIDs */ + if (client->cmd_uid) + client_sync_full(client); + else + client_sync_without_expunges(client); + client_send_tagline(client, "OK Search completed."); } else { - if (client->mailbox->search(client->mailbox, charset, - sargs, NULL, MAIL_THREAD_NONE, - client->output, client->cmd_uid)) { - /* NOTE: syncing is allowed when returning UIDs */ - if (client->cmd_uid) - client_sync_full(client); - else - client_sync_without_expunges(client); - client_send_tagline(client, "OK Search completed."); - } else { - client_send_storage_error(client); - } + client_send_storage_error(client); } pool_unref(pool); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/cmd-sort.c --- a/src/imap/cmd-sort.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/cmd-sort.c Mon Jan 20 16:52:51 2003 +0200 @@ -3,8 +3,8 @@ #include "common.h" #include "buffer.h" #include "commands.h" -#include "mail-search.h" -#include "mail-sort.h" +#include "imap-search.h" +#include "imap-sort.h" struct sort_name { enum mail_sort_type type; @@ -110,23 +110,19 @@ pool = pool_alloconly_create("mail_search_args", 2048); - sargs = mail_search_args_build(pool, args, &error); + sargs = imap_search_args_build(pool, args, &error); if (sargs == NULL) { /* error in search arguments */ client_send_tagline(client, t_strconcat("NO ", error, NULL)); + } else if (imap_sort(client, charset, sargs, sorting)) { + /* NOTE: syncing is allowed when returning UIDs */ + if (client->cmd_uid) + client_sync_full(client); + else + client_sync_without_expunges(client); + client_send_tagline(client, "OK Sort completed."); } else { - if (client->mailbox->search(client->mailbox, charset, - sargs, sorting, MAIL_THREAD_NONE, - client->output, client->cmd_uid)) { - /* NOTE: syncing is allowed when returning UIDs */ - if (client->cmd_uid) - client_sync_full(client); - else - client_sync_without_expunges(client); - client_send_tagline(client, "OK Search completed."); - } else { - client_send_storage_error(client); - } + client_send_storage_error(client); } pool_unref(pool); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/cmd-store.c --- a/src/imap/cmd-store.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/cmd-store.c Mon Jan 20 16:52:51 2003 +0200 @@ -35,9 +35,8 @@ int cmd_store(struct client *client) { struct imap_arg *args; - enum mail_flags flags; + struct mail_full_flags flags; enum modify_type modify_type; - const char *custflags[MAIL_CUSTOM_FLAGS_COUNT]; const char *messageset, *item; int silent, all_found; @@ -62,19 +61,17 @@ if (args[2].type == IMAP_ARG_LIST) { if (!client_parse_mail_flags(client, IMAP_ARG_LIST(&args[2])->args, - IMAP_ARG_LIST(&args[2])->size, - &flags, custflags)) + &flags)) return TRUE; } else { - if (!client_parse_mail_flags(client, &args[2], 1, - &flags, custflags)) + if (!client_parse_mail_flags(client, args+2, &flags)) return TRUE; } /* and update the flags */ client->sync_flags_send_uid = client->cmd_uid; if (client->mailbox->update_flags(client->mailbox, messageset, - client->cmd_uid, flags, custflags, + client->cmd_uid, &flags, modify_type, !silent, &all_found)) { /* NOTE: syncing isn't allowed here */ client_sync_without_expunges(client); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/cmd-thread.c --- a/src/imap/cmd-thread.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/cmd-thread.c Mon Jan 20 16:52:51 2003 +0200 @@ -3,8 +3,8 @@ #include "common.h" #include "buffer.h" #include "commands.h" -#include "mail-search.h" -#include "mail-sort.h" +#include "imap-search.h" +#include "imap-thread.h" int cmd_thread(struct client *client) { @@ -58,23 +58,19 @@ pool = pool_alloconly_create("mail_search_args", 2048); - sargs = mail_search_args_build(pool, args, &error); + sargs = imap_search_args_build(pool, args, &error); if (sargs == NULL) { /* error in search arguments */ client_send_tagline(client, t_strconcat("NO ", error, NULL)); + } else if (imap_thread(client, charset, sargs, threading)) { + /* NOTE: syncing is allowed when returning UIDs */ + if (client->cmd_uid) + client_sync_full(client); + else + client_sync_without_expunges(client); + client_send_tagline(client, "OK Search completed."); } else { - if (client->mailbox->search(client->mailbox, charset, - sargs, NULL, threading, - client->output, client->cmd_uid)) { - /* NOTE: syncing is allowed when returning UIDs */ - if (client->cmd_uid) - client_sync_full(client); - else - client_sync_without_expunges(client); - client_send_tagline(client, "OK Search completed."); - } else { - client_send_storage_error(client); - } + client_send_storage_error(client); } pool_unref(pool); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/commands-util.c --- a/src/imap/commands-util.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/commands-util.c Mon Jan 20 16:52:51 2003 +0200 @@ -122,37 +122,39 @@ } int client_parse_mail_flags(struct client *client, struct imap_arg *args, - size_t args_count, enum mail_flags *flags, - const char *custflags[MAIL_CUSTOM_FLAGS_COUNT]) + struct mail_full_flags *flags) { + /* @UNSAFE */ char *atom; - size_t pos; - int i, custpos; + size_t max_flags, flag_pos, i; + + max_flags = MAIL_CUSTOM_FLAGS_COUNT; - memset(custflags, 0, sizeof(const char *) * MAIL_CUSTOM_FLAGS_COUNT); + memset(flags, 0, sizeof(*flags)); + flags->custom_flags = t_new(const char *, flags->custom_flags_count); - *flags = 0; custpos = 0; - for (pos = 0; pos < args_count; pos++) { - if (args[pos].type != IMAP_ARG_ATOM) { + flag_pos = 0; + while (args->type != IMAP_ARG_EOL) { + if (args->type != IMAP_ARG_ATOM) { client_send_command_error(client, "Flags list contains non-atoms."); return FALSE; } - atom = IMAP_ARG_STR(&args[pos]); + atom = IMAP_ARG_STR(args); if (*atom == '\\') { /* system flag */ str_ucase(atom); if (strcmp(atom, "\\ANSWERED") == 0) - *flags |= MAIL_ANSWERED; + flags->flags |= MAIL_ANSWERED; else if (strcmp(atom, "\\FLAGGED") == 0) - *flags |= MAIL_FLAGGED; + flags->flags |= MAIL_FLAGGED; else if (strcmp(atom, "\\DELETED") == 0) - *flags |= MAIL_DELETED; + flags->flags |= MAIL_DELETED; else if (strcmp(atom, "\\SEEN") == 0) - *flags |= MAIL_SEEN; + flags->flags |= MAIL_SEEN; else if (strcmp(atom, "\\DRAFT") == 0) - *flags |= MAIL_DRAFT; + flags->flags |= MAIL_DRAFT; else { client_send_tagline(client, t_strconcat( "BAD Invalid system flag ", @@ -161,26 +163,30 @@ } } else { /* custom flag - first make sure it's not a duplicate */ - for (i = 0; i < custpos; i++) { - if (strcasecmp(custflags[i], atom) == 0) + for (i = 0; i < flag_pos; i++) { + if (strcasecmp(flags->custom_flags[i], + atom) == 0) break; } - if (i == MAIL_CUSTOM_FLAGS_COUNT) { + if (i == max_flags) { client_send_tagline(client, "Maximum number of different custom " "flags exceeded"); return FALSE; } - if (i == custpos) { - *flags |= 1 << (custpos + - MAIL_CUSTOM_FLAG_1_BIT); - custflags[custpos++] = atom; + if (i == flags->custom_flags_count) { + flags->flags |= 1 << (flag_pos + + MAIL_CUSTOM_FLAG_1_BIT); + flags->custom_flags[flag_pos++] = atom; } } + + args++; } + flags->custom_flags_count = flag_pos; return TRUE; } diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/commands-util.h --- a/src/imap/commands-util.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/imap/commands-util.h Mon Jan 20 16:52:51 2003 +0200 @@ -24,12 +24,10 @@ /* Send last mail storage error message to client. */ void client_send_storage_error(struct client *client); -/* Parse flags, stores custom flag names into custflags[]. The names point to - strings in ImapArgList. Returns TRUE if successful, if not sends an error - message to client. */ +/* Parse flags. Returns TRUE if successful, if not sends an error message to + client. */ int client_parse_mail_flags(struct client *client, struct imap_arg *args, - size_t args_count, enum mail_flags *flags, - const char *custflags[MAIL_CUSTOM_FLAGS_COUNT]); + struct mail_full_flags *flags); /* Send FLAGS + PERMANENTFLAGS to client. */ void client_send_mailbox_flags(struct client *client, struct mailbox *box, diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-fetch-body-section.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-fetch-body-section.c Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,461 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "message-parser.h" +#include "message-send.h" +#include "mail-storage.h" +#include "imap-fetch.h" + +#include +#include + +/* For FETCH[HEADER.FIELDS*] we need to modify the header data before sending + it. We can either save it in memory and then send it, or we can parse it + twice, first calculating the size and then send it. This value specifies + the maximum amount of memory we allow to allocate before using + double-parsing. */ +#define MAX_HEADER_BUFFER_SIZE (32*1024) + +#define UNSIGNED_CRLF (const unsigned char *) "\r\n" + +struct fetch_header_field_context { + string_t *dest; + struct ostream *output; + uoff_t dest_size; + + uoff_t skip, max_size; + const char *const *fields; + int (*match_func) (const char *const *, const unsigned char *, size_t); +}; + +/* fetch BODY[] or BODY[TEXT] */ +static int fetch_body(struct imap_fetch_context *ctx, + const struct imap_fetch_body_data *body, + struct mail *mail, int fetch_header) +{ + struct message_size hdr_size, body_size; + struct istream *stream; + const char *str; + + stream = mail->get_stream(mail, &hdr_size, &body_size); + if (stream == NULL) + return FALSE; + + if (!fetch_header) + i_stream_seek(stream, hdr_size.physical_size); + else + message_size_add(&body_size, &hdr_size); + + str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", + ctx->prefix, body_size.virtual_size); + if (o_stream_send_str(ctx->output, str) < 0) + return FALSE; + + /* FIXME: SLOW! we need some cache for this. */ + return message_send(ctx->output, stream, &body_size, + body->skip, body->max_size); +} + +static const char **get_fields_array(const char *fields) +{ + const char **field_list, **field; + + while (*fields == ' ') + fields++; + if (*fields == '(') + fields++; + + field_list = t_strsplit(fields, " )"); + + /* array ends at ")" element */ + for (field = field_list; *field != NULL; field++) { + if (strcmp(*field, ")") == 0) + *field = NULL; + } + + return field_list; +} + +static int header_match(const char *const *fields, + const unsigned char *name, size_t size) +{ + const unsigned char *name_start, *name_end; + const char *field; + + if (size == 0) + return FALSE; + + name_start = name; + name_end = name + size; + + for (; *fields != NULL; fields++) { + field = *fields; + if (*field == '\0') + continue; + + for (name = name_start; name != name_end; name++) { + /* field has been uppercased long time ago while + parsing FETCH command */ + if (i_toupper(*name) != *field) + break; + + field++; + if (*field == '\0') { + if (name+1 == name_end) + return TRUE; + break; + } + } + } + + return FALSE; +} + +static int header_match_not(const char *const *fields, + const unsigned char *name, size_t size) +{ + return !header_match(fields, name, size); +} + +static int header_match_mime(const char *const *fields __attr_unused__, + const unsigned char *name, size_t size) +{ + if (size > 8 && memcasecmp(name, "Content-", 8) == 0) + return TRUE; + + if (size == 12 && memcasecmp(name, "Mime-Version", 12) == 0) + return TRUE; + + return FALSE; +} + +static int fetch_header_append(struct fetch_header_field_context *ctx, + const unsigned char *str, size_t size) +{ + if (ctx->skip > 0) { + if (ctx->skip >= size) { + ctx->skip -= size; + return TRUE; + } + + str += ctx->skip; + size -= ctx->skip; + ctx->skip = 0; + } + + if (ctx->dest_size + size > ctx->max_size) { + i_assert(ctx->dest_size <= ctx->max_size); + size = ctx->max_size - ctx->dest_size; + } + + if (ctx->dest != NULL) + str_append_n(ctx->dest, str, size); + ctx->dest_size += size; + + if (ctx->output != NULL) { + if (o_stream_send(ctx->output, str, size) < 0) + return FALSE; + } + return ctx->dest_size < ctx->max_size; +} + +static void fetch_header_field(struct message_part *part __attr_unused__, + const unsigned char *name, size_t name_len, + const unsigned char *value __attr_unused__, + size_t value_len __attr_unused__, + void *context) +{ + struct fetch_header_field_context *ctx = context; + const unsigned char *field_start, *field_end, *cr, *p; + + /* see if we want this field. */ + if (!ctx->match_func(ctx->fields, name, name_len) || name_len == 0) + return; + + /* add the field, inserting CRs when needed. FIXME: is this too + kludgy? we assume name continues with ": value". but otherwise + we wouldn't reply with correct LWSP around ":". */ + field_start = name; + field_end = value + value_len; + + cr = NULL; + for (p = field_start; p != field_end; p++) { + if (*p == '\r') + cr = p; + else if (*p == '\n' && cr != p-1) { + /* missing CR */ + if (!fetch_header_append(ctx, field_start, + (size_t) (p-field_start))) + return; + if (!fetch_header_append(ctx, UNSIGNED_CRLF, 2)) + return; + + field_start = p+1; + } + } + + if (field_start != field_end) { + if (!fetch_header_append(ctx, field_start, + (size_t) (field_end-field_start))) + return; + } + + (void)fetch_header_append(ctx, UNSIGNED_CRLF, 2); +} + +static int fetch_header_fields(struct istream *input, const char *section, + struct fetch_header_field_context *ctx) +{ + if (strncmp(section, "HEADER.FIELDS ", 14) == 0) { + ctx->fields = get_fields_array(section + 14); + ctx->match_func = header_match; + } else if (strncmp(section, "HEADER.FIELDS.NOT ", 18) == 0) { + ctx->fields = get_fields_array(section + 18); + ctx->match_func = header_match_not; + } else if (strcmp(section, "MIME") == 0) { + /* Mime-Version + Content-* fields */ + ctx->match_func = header_match_mime; + } else { + i_warning("BUG: Accepted invalid section from user: '%s'", + section); + return FALSE; + } + + ctx->dest_size = 0; + message_parse_header(NULL, input, NULL, fetch_header_field, ctx); + + /* FIXME: The blank line must not be filtered, says RFC. However, we + shouldn't add it if it wasn't there in the first place. Not very + easy to know currently so we'll just do it always, it'll be present + in all sane messages anyway.. */ + (void)fetch_header_append(ctx, UNSIGNED_CRLF, 2); + + i_assert(ctx->dest_size <= ctx->max_size); + i_assert(ctx->dest == NULL || str_len(ctx->dest) == ctx->dest_size); + return TRUE; +} + +/* fetch wanted headers from given data */ +static int fetch_header_from(struct imap_fetch_context *ctx, + struct istream *input, + const struct message_size *size, + const struct imap_fetch_body_data *body) +{ + struct fetch_header_field_context hdr_ctx; + const char *str; + uoff_t start_offset; + int failed; + + /* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */ + + if (strcmp(body->section, "HEADER") == 0) { + /* all headers */ + str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", + ctx->prefix, size->virtual_size); + if (o_stream_send_str(ctx->output, str) < 0) + return FALSE; + return message_send(ctx->output, input, size, + body->skip, body->max_size); + } + + /* partial headers - copy the wanted fields into memory, inserting + missing CRs on the way. If the header is too large, calculate + the size first and then send the data directly to output stream. */ + + memset(&hdr_ctx, 0, sizeof(hdr_ctx)); + hdr_ctx.skip = body->skip; + hdr_ctx.max_size = body->max_size; + + failed = FALSE; + start_offset = input->v_offset; + + t_push(); + + /* first pass, we need at least the size */ + if (size->virtual_size > MAX_HEADER_BUFFER_SIZE && + body->max_size > MAX_HEADER_BUFFER_SIZE) { + if (!fetch_header_fields(input, body->section, &hdr_ctx)) + failed = TRUE; + + i_assert(hdr_ctx.dest_size <= size->virtual_size); + } else { + hdr_ctx.dest = t_str_new(size->virtual_size < 8192 ? + size->virtual_size : 8192); + if (!fetch_header_fields(input, body->section, &hdr_ctx)) + failed = TRUE; + } + + if (!failed) { + str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", + ctx->prefix, hdr_ctx.dest_size); + if (o_stream_send_str(ctx->output, str) < 0) + failed = TRUE; + } + + if (!failed) { + if (hdr_ctx.dest == NULL) { + /* second pass, write the data to output stream */ + uoff_t first_size = hdr_ctx.dest_size; + + hdr_ctx.output = ctx->output; + i_stream_seek(input, start_offset); + + if (!failed && + !fetch_header_fields(input, body->section, + &hdr_ctx)) + failed = TRUE; + + i_assert(first_size == hdr_ctx.dest_size); + } else { + if (o_stream_send(ctx->output, str_data(hdr_ctx.dest), + str_len(hdr_ctx.dest)) < 0) + failed = TRUE; + } + } + + t_pop(); + return !failed; +} + +static int fetch_header(struct imap_fetch_context *ctx, struct mail *mail, + const struct imap_fetch_body_data *body) +{ + struct istream *stream; + struct message_size hdr_size; + + stream = mail->get_stream(mail, &hdr_size, NULL); + if (stream == NULL) + return FALSE; + + return fetch_header_from(ctx, stream, &hdr_size, body); +} + +/* Find message_part for section (eg. 1.3.4) */ +static const struct message_part * +part_find(struct mail *mail, const struct imap_fetch_body_data *body, + const char **section) +{ + const struct message_part *part; + const char *path; + unsigned int num; + + part = mail->get_parts(mail); + if (part == NULL) + return NULL; + + path = body->section; + while (*path >= '0' && *path <= '9' && part != NULL) { + /* get part number */ + num = 0; + while (*path != '\0' && *path != '.') { + if (*path < '0' || *path > '9') + return NULL; + num = num*10 + (*path - '0'); + path++; + } + + if (*path == '.') + path++; + + if (part->flags & MESSAGE_PART_FLAG_MULTIPART) { + /* find the part */ + part = part->children; + for (; num > 1 && part != NULL; num--) + part = part->next; + } else { + /* only 1 allowed with non-multipart messages */ + if (num != 1) + return NULL; + } + + if (part != NULL && + (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822)) { + /* skip the message/rfc822 part */ + part = part->children; + } + } + + *section = path; + return part; +} + +/* fetch BODY[1.2] or BODY[1.2.TEXT] */ +static int fetch_part_body(struct imap_fetch_context *ctx, + struct istream *stream, + const struct imap_fetch_body_data *body, + const struct message_part *part) +{ + const char *str; + + /* jump to beginning of part body */ + i_stream_seek(stream, part->physical_pos + + part->header_size.physical_size); + + str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", + ctx->prefix, part->body_size.virtual_size); + if (o_stream_send_str(ctx->output, str) < 0) + return FALSE; + + /* FIXME: potential performance problem with big messages: + FETCH BODY[1]<100000..1024>, hopefully no clients do this */ + return message_send(ctx->output, stream, &part->body_size, + body->skip, body->max_size); +} + +static int fetch_part(struct imap_fetch_context *ctx, struct mail *mail, + const struct imap_fetch_body_data *body) +{ + struct istream *stream; + const struct message_part *part; + const char *section; + + part = part_find(mail, body, §ion); + if (part == NULL) + return FALSE; + + stream = mail->get_stream(mail, NULL, NULL); + if (stream == NULL) + return FALSE; + + if (*section == '\0' || strcmp(section, "TEXT") == 0) + return fetch_part_body(ctx, stream, body, part); + + if (strncmp(section, "HEADER", 6) == 0 || + strcmp(section, "MIME") == 0) { + i_stream_seek(stream, part->physical_pos); + return fetch_header_from(ctx, stream, &part->header_size, body); + } + + i_warning("BUG: Accepted invalid section from user: '%s'", + body->section); + return FALSE; +} + +int imap_fetch_body_section(struct imap_fetch_context *ctx, + const struct imap_fetch_body_data *body, + struct mail *mail) +{ + ctx->prefix = !body->skip_set ? + t_strdup_printf(" BODY[%s]", body->section) : + t_strdup_printf(" BODY[%s]<%"PRIuUOFF_T">", + body->section, body->skip); + if (ctx->first) { + ctx->prefix++; ctx->first = FALSE; + } + + if (*body->section == '\0') + return fetch_body(ctx, body, mail, TRUE); + if (strcmp(body->section, "TEXT") == 0) + return fetch_body(ctx, body, mail, FALSE); + if (strncmp(body->section, "HEADER", 6) == 0) + return fetch_header(ctx, mail, body); + if (*body->section >= '0' && *body->section <= '9') + return fetch_part(ctx, mail, body); + + i_warning("BUG: Accepted invalid section from user: '%s'", + body->section); + return FALSE; +} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-fetch.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-fetch.c Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,279 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "istream.h" +#include "ostream.h" +#include "str.h" +#include "message-send.h" +#include "message-size.h" +#include "imap-date.h" +#include "commands.h" +#include "imap-fetch.h" + +#include + +static void fetch_uid(struct imap_fetch_context *ctx, struct mail *mail) +{ + str_printfa(ctx->str, "UID %u ", mail->uid); +} + +static int fetch_flags(struct imap_fetch_context *ctx, struct mail *mail) +{ + const struct mail_full_flags *flags; + + flags = mail->get_flags(mail); + if (flags == NULL) + return FALSE; + + str_printfa(ctx->str, "FLAGS (%s) ", + imap_write_flags(flags->flags, flags->custom_flags, + flags->custom_flags_count)); + return TRUE; +} + +static int fetch_internaldate(struct imap_fetch_context *ctx, struct mail *mail) +{ + time_t time; + + time = mail->get_received_date(mail); + if (time == (time_t)-1) + return FALSE; + + str_printfa(ctx->str, "INTERNALDATE \"%s\" ", imap_to_datetime(time)); + return TRUE; +} + +static int fetch_rfc822_size(struct imap_fetch_context *ctx, struct mail *mail) +{ + uoff_t size; + + size = mail->get_size(mail); + if (size == (uoff_t)-1) + return FALSE; + + str_printfa(ctx->str, "RFC822.SIZE %"PRIuUOFF_T" ", size); + return TRUE; +} + +static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail) +{ + const char *body; + + body = mail->get_special(mail, MAIL_FETCH_IMAP_BODY); + if (body == NULL) + return FALSE; + + str_printfa(ctx->str, "BODY (%s) ", body); + return TRUE; +} + +static int fetch_bodystructure(struct imap_fetch_context *ctx, + struct mail *mail) +{ + const char *bodystructure; + + bodystructure = mail->get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE); + if (bodystructure == NULL) + return FALSE; + + str_printfa(ctx->str, "BODYSTRUCTURE (%s) ", bodystructure); + return TRUE; +} + +static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail) +{ + const char *envelope; + + envelope = mail->get_special(mail, MAIL_FETCH_IMAP_ENVELOPE); + if (envelope == NULL) + return FALSE; + + str_printfa(ctx->str, "ENVELOPE (%s) ", envelope); + return TRUE; +} + +static int fetch_send_rfc822(struct imap_fetch_context *ctx, struct mail *mail) +{ + struct message_size hdr_size, body_size; + struct istream *stream; + const char *str; + + stream = mail->get_stream(mail, &hdr_size, &body_size); + if (stream == NULL) + return FALSE; + + message_size_add(&body_size, &hdr_size); + + str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n", + body_size.virtual_size); + if (ctx->first) { + str++; ctx->first = FALSE; + } + if (o_stream_send_str(ctx->output, str) < 0) + return FALSE; + + return message_send(ctx->output, stream, &body_size, 0, (uoff_t)-1); +} + +static int fetch_send_rfc822_header(struct imap_fetch_context *ctx, + struct mail *mail) +{ + struct message_size hdr_size; + struct istream *stream; + const char *str; + + stream = mail->get_stream(mail, &hdr_size, NULL); + if (stream == NULL) + return FALSE; + + str = t_strdup_printf(" RFC822.HEADER {%"PRIuUOFF_T"}\r\n", + hdr_size.virtual_size); + if (ctx->first) { + str++; ctx->first = FALSE; + } + if (o_stream_send_str(ctx->output, str) < 0) + return FALSE; + + return message_send(ctx->output, stream, &hdr_size, 0, (uoff_t)-1); +} + +static int fetch_send_rfc822_text(struct imap_fetch_context *ctx, + struct mail *mail) +{ + struct message_size hdr_size, body_size; + struct istream *stream; + const char *str; + + stream = mail->get_stream(mail, &hdr_size, &body_size); + if (stream == NULL) + return FALSE; + + str = t_strdup_printf(" RFC822.TEXT {%"PRIuUOFF_T"}\r\n", + body_size.virtual_size); + if (ctx->first) { + str++; ctx->first = FALSE; + } + if (o_stream_send_str(ctx->output, str) < 0) + return FALSE; + + i_stream_seek(stream, hdr_size.physical_size); + return message_send(ctx->output, stream, &body_size, 0, (uoff_t)-1); +} + +static int fetch_mail(struct imap_fetch_context *ctx, struct mail *mail) +{ + struct imap_fetch_body_data *body; + size_t len, orig_len; + int failed, data_written; + + str_truncate(ctx->str, 0); + str_printfa(ctx->str, "* %u FETCH (", mail->seq); + orig_len = str_len(ctx->str); + + failed = TRUE; + data_written = FALSE; + do { + /* write the data into temp string */ + if (ctx->imap_data & IMAP_FETCH_UID) + fetch_uid(ctx, mail); + if ((ctx->fetch_data & MAIL_FETCH_FLAGS) || mail->seen_updated) + fetch_flags(ctx, mail); + if (ctx->fetch_data & MAIL_FETCH_RECEIVED_DATE) + fetch_internaldate(ctx, mail); + if (ctx->fetch_data & MAIL_FETCH_SIZE) + fetch_rfc822_size(ctx, mail); + if (ctx->fetch_data & MAIL_FETCH_IMAP_BODY) + fetch_body(ctx, mail); + if (ctx->fetch_data & MAIL_FETCH_IMAP_BODYSTRUCTURE) + fetch_bodystructure(ctx, mail); + if (ctx->fetch_data & MAIL_FETCH_IMAP_ENVELOPE) + fetch_envelope(ctx, mail); + + /* send the data written into temp string */ + len = str_len(ctx->str); + ctx->first = len == orig_len; + + if (!ctx->first) + str_truncate(ctx->str, --len); + if (o_stream_send(ctx->output, str_data(ctx->str), len) < 0) + break; + + data_written = TRUE; + + /* large data */ + if (ctx->imap_data & IMAP_FETCH_RFC822) + if (!fetch_send_rfc822(ctx, mail)) + break; + if (ctx->imap_data & IMAP_FETCH_RFC822_HEADER) + if (!fetch_send_rfc822_header(ctx, mail)) + break; + if (ctx->imap_data & IMAP_FETCH_RFC822_TEXT) + if (!fetch_send_rfc822_text(ctx, mail)) + break; + + for (body = ctx->bodies; body != NULL; body = body->next) { + if (!imap_fetch_body_section(ctx, body, mail)) + break; + } + + failed = FALSE; + } while (0); + + if (data_written) { + if (o_stream_send(ctx->output, ")\r\n", 3) < 0) + failed = TRUE; + } + + return !failed; +} + +int imap_fetch(struct client *client, + enum mail_fetch_field fetch_data, + enum imap_fetch_field imap_data, + struct imap_fetch_body_data *bodies, + const char *messageset, int uidset) +{ + struct imap_fetch_context ctx; + struct mail *mail; + int all_found, update_seen = FALSE; + + if (!client->mailbox->readonly) { + /* If we have any BODY[..] sections, \Seen flag is added for + all messages */ + struct imap_fetch_body_data *body; + + for (body = bodies; body != NULL; body = body->next) { + if (!body->peek) { + update_seen = TRUE; + break; + } + } + + if (imap_data & (IMAP_FETCH_RFC822|IMAP_FETCH_RFC822_TEXT)) + update_seen = TRUE; + } + + memset(&ctx, 0, sizeof(ctx)); + ctx.fetch_data = fetch_data; + ctx.imap_data = imap_data; + ctx.bodies = bodies; + ctx.output = client->output; + ctx.str = t_str_new(8192); + + ctx.fetch_ctx = client->mailbox-> + fetch_init(client->mailbox, fetch_data, &update_seen, + messageset, uidset); + if (ctx.fetch_ctx == NULL) + return -1; + + while ((mail = client->mailbox->fetch_next(ctx.fetch_ctx)) != NULL) { + if (!fetch_mail(&ctx, mail)) { + ctx.failed = TRUE; + break; + } + } + + if (!client->mailbox->fetch_deinit(ctx.fetch_ctx, &all_found)) + return -1; + return ctx.failed ? -1 : all_found; +} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-fetch.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-fetch.h Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,45 @@ +#ifndef __IMAP_FETCH_H +#define __IMAP_FETCH_H + +enum imap_fetch_field { + IMAP_FETCH_UID = 0x01, + IMAP_FETCH_RFC822 = 0x02, + IMAP_FETCH_RFC822_HEADER = 0x04, + IMAP_FETCH_RFC822_TEXT = 0x08 +}; + +struct imap_fetch_body_data { + struct imap_fetch_body_data *next; + + const char *section; /* NOTE: always uppercased */ + uoff_t skip, max_size; /* if you don't want max_size, + set it to (uoff_t)-1 */ + unsigned int skip_set:1; + unsigned int peek:1; +}; + +struct imap_fetch_context { + struct mail_fetch_context *fetch_ctx; + + enum mail_fetch_field fetch_data; + enum imap_fetch_field imap_data; + struct imap_fetch_body_data *bodies; + + string_t *str; + struct ostream *output; + const char *prefix; + + int first, failed; +}; + +int imap_fetch(struct client *client, + enum mail_fetch_field fetch_data, + enum imap_fetch_field imap_data, + struct imap_fetch_body_data *bodies, + const char *messageset, int uidset); + +int imap_fetch_body_section(struct imap_fetch_context *ctx, + const struct imap_fetch_body_data *body, + struct mail *mail); + +#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-search.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-search.c Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,377 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "mail-search.h" +#include "imap-search.h" + +struct search_build_data { + pool_t pool; + const char *error; +}; + +static struct mail_search_arg * +search_arg_new(pool_t pool, enum mail_search_arg_type type) +{ + struct mail_search_arg *arg; + + arg = p_new(pool, struct mail_search_arg, 1); + arg->type = type; + + return arg; +} + +#define ARG_NEW(type, value) \ + arg_new(data, args, next_sarg, type, value) + +static int arg_new(struct search_build_data *data, struct imap_arg **args, + struct mail_search_arg **next_sarg, + enum mail_search_arg_type type, int value) +{ + struct mail_search_arg *sarg; + + *next_sarg = sarg = search_arg_new(data->pool, type); + if (value == 0) + return TRUE; + + /* first arg */ + if ((*args)->type == IMAP_ARG_EOL) { + data->error = "Missing parameter for argument"; + return FALSE; + } + + if ((*args)->type != IMAP_ARG_ATOM && + (*args)->type != IMAP_ARG_STRING) { + data->error = "Invalid parameter for argument"; + return FALSE; + } + + sarg->value.str = str_ucase(IMAP_ARG_STR(*args)); + *args += 1; + + /* second arg */ + if (value == 2) { + if ((*args)->type == IMAP_ARG_EOL) { + data->error = "Missing parameter for argument"; + return FALSE; + } + + if ((*args)->type != IMAP_ARG_ATOM && + (*args)->type != IMAP_ARG_STRING) { + data->error = "Invalid parameter for argument"; + return FALSE; + } + + sarg->hdr_field_name = sarg->value.str; + sarg->value.str = str_ucase(IMAP_ARG_STR(*args)); + *args += 1; + } + + return TRUE; +} + +static int search_arg_build(struct search_build_data *data, + struct imap_arg **args, + struct mail_search_arg **next_sarg) +{ + struct mail_search_arg **subargs; + struct imap_arg *arg; + char *str; + + if ((*args)->type == IMAP_ARG_EOL) { + data->error = "Missing argument"; + return FALSE; + } + + arg = *args; + + if (arg->type == IMAP_ARG_NIL) { + /* NIL not allowed */ + data->error = "NIL not allowed"; + return FALSE; + } + + if (arg->type == IMAP_ARG_LIST) { + struct imap_arg *listargs = IMAP_ARG_LIST(arg)->args; + + *next_sarg = search_arg_new(data->pool, SEARCH_SUB); + subargs = &(*next_sarg)->value.subargs; + while (listargs->type != IMAP_ARG_EOL) { + if (!search_arg_build(data, &listargs, subargs)) + return FALSE; + subargs = &(*subargs)->next; + } + + *args += 1; + return TRUE; + } + + i_assert(arg->type == IMAP_ARG_ATOM || + arg->type == IMAP_ARG_STRING); + + /* string argument - get the name and jump to next */ + str = IMAP_ARG_STR(arg); + *args += 1; + str_ucase(str); + + switch (*str) { + case 'A': + if (strcmp(str, "ANSWERED") == 0) + return ARG_NEW(SEARCH_ANSWERED, 0); + else if (strcmp(str, "ALL") == 0) + return ARG_NEW(SEARCH_ALL, 0); + break; + case 'B': + if (strcmp(str, "BODY") == 0) { + /* */ + return ARG_NEW(SEARCH_BODY, 1); + } else if (strcmp(str, "BEFORE") == 0) { + /* */ + return ARG_NEW(SEARCH_BEFORE, 1); + } else if (strcmp(str, "BCC") == 0) { + /* */ + return ARG_NEW(SEARCH_BCC, 1); + } + break; + case 'C': + if (strcmp(str, "CC") == 0) { + /* */ + return ARG_NEW(SEARCH_CC, 1); + } + break; + case 'D': + if (strcmp(str, "DELETED") == 0) + return ARG_NEW(SEARCH_DELETED, 0); + else if (strcmp(str, "DRAFT") == 0) + return ARG_NEW(SEARCH_DRAFT, 0); + break; + case 'F': + if (strcmp(str, "FLAGGED") == 0) + return ARG_NEW(SEARCH_FLAGGED, 0); + else if (strcmp(str, "FROM") == 0) { + /* */ + return ARG_NEW(SEARCH_FROM, 1); + } + break; + case 'H': + if (strcmp(str, "HEADER") == 0) { + /* */ + const char *key; + + if ((*args)->type == IMAP_ARG_EOL) { + data->error = "Missing parameter for HEADER"; + return FALSE; + } + if ((*args)->type != IMAP_ARG_ATOM && + (*args)->type != IMAP_ARG_STRING) { + data->error = "Invalid parameter for HEADER"; + return FALSE; + } + + key = str_ucase(IMAP_ARG_STR(*args)); + + if (strcmp(key, "FROM") == 0) { + *args += 1; + return ARG_NEW(SEARCH_FROM, 1); + } else if (strcmp(key, "TO") == 0) { + *args += 1; + return ARG_NEW(SEARCH_TO, 1); + } else if (strcmp(key, "CC") == 0) { + *args += 1; + return ARG_NEW(SEARCH_CC, 1); + } else if (strcmp(key, "BCC") == 0) { + *args += 1; + return ARG_NEW(SEARCH_BCC, 1); + } else if (strcmp(key, "SUBJECT") == 0) { + *args += 1; + return ARG_NEW(SEARCH_SUBJECT, 1); + } else if (strcmp(key, "IN-REPLY-TO") == 0) { + *args += 1; + return ARG_NEW(SEARCH_IN_REPLY_TO, 1); + } else if (strcmp(key, "MESSAGE-ID") == 0) { + *args += 1; + return ARG_NEW(SEARCH_MESSAGE_ID, 1); + } else { + return ARG_NEW(SEARCH_HEADER, 2); + } + } + break; + case 'K': + if (strcmp(str, "KEYWORD") == 0) { + /* */ + return ARG_NEW(SEARCH_KEYWORD, 1); + } + break; + case 'L': + if (strcmp(str, "LARGER") == 0) { + /* */ + return ARG_NEW(SEARCH_LARGER, 1); + } + break; + case 'N': + if (strcmp(str, "NOT") == 0) { + if (!search_arg_build(data, args, next_sarg)) + return FALSE; + (*next_sarg)->not = !(*next_sarg)->not; + return TRUE; + } else if (strcmp(str, "NEW") == 0) { + /* NEW == (RECENT UNSEEN) */ + *next_sarg = search_arg_new(data->pool, SEARCH_SUB); + + subargs = &(*next_sarg)->value.subargs; + *subargs = search_arg_new(data->pool, SEARCH_RECENT); + (*subargs)->next = search_arg_new(data->pool, + SEARCH_SEEN); + (*subargs)->next->not = TRUE; + return TRUE; + } + break; + case 'O': + if (strcmp(str, "OR") == 0) { + /* */ + *next_sarg = search_arg_new(data->pool, SEARCH_OR); + + subargs = &(*next_sarg)->value.subargs; + for (;;) { + if (!search_arg_build(data, args, subargs)) + return FALSE; + + subargs = &(*subargs)->next; + + /* OR OR ... - put them all + under one SEARCH_OR list. */ + if ((*args)->type == IMAP_ARG_EOL) + break; + + if ((*args)->type != IMAP_ARG_ATOM || + strcasecmp(IMAP_ARG_STR(*args), "OR") != 0) + break; + + *args += 1; + } + + if (!search_arg_build(data, args, subargs)) + return FALSE; + return TRUE; + } if (strcmp(str, "ON") == 0) { + /* */ + return ARG_NEW(SEARCH_ON, 1); + } if (strcmp(str, "OLD") == 0) { + /* OLD == NOT RECENT */ + if (!ARG_NEW(SEARCH_RECENT, 0)) + return FALSE; + + (*next_sarg)->not = TRUE; + return TRUE; + } + break; + case 'R': + if (strcmp(str, "RECENT") == 0) + return ARG_NEW(SEARCH_RECENT, 0); + break; + case 'S': + if (strcmp(str, "SEEN") == 0) + return ARG_NEW(SEARCH_SEEN, 0); + else if (strcmp(str, "SUBJECT") == 0) { + /* */ + return ARG_NEW(SEARCH_SUBJECT, 1); + } else if (strcmp(str, "SENTBEFORE") == 0) { + /* */ + return ARG_NEW(SEARCH_SENTBEFORE, 1); + } else if (strcmp(str, "SENTON") == 0) { + /* */ + return ARG_NEW(SEARCH_SENTON, 1); + } else if (strcmp(str, "SENTSINCE") == 0) { + /* */ + return ARG_NEW(SEARCH_SENTSINCE, 1); + } else if (strcmp(str, "SINCE") == 0) { + /* */ + return ARG_NEW(SEARCH_SINCE, 1); + } else if (strcmp(str, "SMALLER") == 0) { + /* */ + return ARG_NEW(SEARCH_SMALLER, 1); + } + break; + case 'T': + if (strcmp(str, "TEXT") == 0) { + /* */ + return ARG_NEW(SEARCH_TEXT, 1); + } else if (strcmp(str, "TO") == 0) { + /* */ + return ARG_NEW(SEARCH_TO, 1); + } + break; + case 'U': + if (strcmp(str, "UID") == 0) { + /* */ + return ARG_NEW(SEARCH_UID, 1); + } else if (strcmp(str, "UNANSWERED") == 0) { + if (!ARG_NEW(SEARCH_ANSWERED, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNDELETED") == 0) { + if (!ARG_NEW(SEARCH_DELETED, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNDRAFT") == 0) { + if (!ARG_NEW(SEARCH_DRAFT, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNFLAGGED") == 0) { + if (!ARG_NEW(SEARCH_FLAGGED, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNKEYWORD") == 0) { + if (!ARG_NEW(SEARCH_KEYWORD, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNSEEN") == 0) { + if (!ARG_NEW(SEARCH_SEEN, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } + break; + default: + if (*str == '*' || (*str >= '0' && *str <= '9')) { + /* */ + if (!ARG_NEW(SEARCH_SET, 0)) + return FALSE; + + (*next_sarg)->value.str = str; + return TRUE; + } + break; + } + + data->error = t_strconcat("Unknown argument ", str, NULL); + return FALSE; +} + +struct mail_search_arg * +imap_search_args_build(pool_t pool, struct imap_arg *args, const char **error) +{ + struct search_build_data data; + struct mail_search_arg *first_sarg, **sargs; + + data.pool = pool; + data.error = NULL; + + /* get the first arg */ + first_sarg = NULL; sargs = &first_sarg; + while (args->type != IMAP_ARG_EOL) { + if (!search_arg_build(&data, &args, sargs)) { + *error = data.error; + return NULL; + } + sargs = &(*sargs)->next; + } + + *error = NULL; + return first_sarg; +} + diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-search.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-search.h Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,8 @@ +#ifndef __IMAP_SEARCH_H +#define __IMAP_SEARCH_H + +/* Builds search arguments based on IMAP arguments. */ +struct mail_search_arg * +imap_search_args_build(pool_t pool, struct imap_arg *args, const char **error); + +#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-sort.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-sort.c Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,690 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +/* Implementation of draft-ietf-imapext-sort-10 sorting algorithm. + Pretty messy code actually, adding any sort types requires care. + This is pretty fast however and takes only as much memory as needed to be + reasonably fast. */ + +#include "common.h" +#include "buffer.h" +#include "hash.h" +#include "ostream.h" +#include "str.h" +#include "imap-base-subject.h" +#include "imap-sort.h" + +#include + +#define MAX_WANTED_HEADERS 10 +#define STRBUF_SIZE 1024 + +#define IS_SORT_STRING(type) \ + ((type) == MAIL_SORT_CC || (type) == MAIL_SORT_FROM || \ + (type) == MAIL_SORT_SUBJECT || (type) == MAIL_SORT_TO) + +#define IS_SORT_TIME(type) \ + ((type) == MAIL_SORT_ARRIVAL || (type) == MAIL_SORT_DATE) + +struct sort_context { + struct mail_search_context *search_ctx; + + enum mail_sort_type sort_program[MAX_SORT_PROGRAM_SIZE]; + enum mail_sort_type common_mask, cache_mask; + + struct mailbox *box; + struct ostream *output; + string_t *str; + + buffer_t *sort_buffer; + size_t sort_element_size; + + pool_t temp_pool, str_pool; + struct hash_table *string_table; + + time_t last_arrival, last_date; + uoff_t last_size; + char *last_cc, *last_from, *last_subject, *last_to; + + int written, id_is_uid; +}; + +static void mail_sort_input(struct sort_context *ctx, struct mail *mail); +static void mail_sort_flush(struct sort_context *ctx); + +static enum mail_sort_type +mail_sort_normalize(const enum mail_sort_type *input, buffer_t *output) +{ + enum mail_sort_type type, mask = 0; + int pos, reverse; + + reverse = FALSE; + for (pos = 0; *input != MAIL_SORT_END; input++) { + if (*input == MAIL_SORT_REVERSE) + reverse = !reverse; + else { + if ((mask & *input) == 0) { + if (reverse) { + type = MAIL_SORT_REVERSE; + buffer_append(output, + &type, sizeof(type)); + } + + buffer_append(output, input, sizeof(*input)); + mask |= *input; + } + + reverse = FALSE; + } + } + + type = MAIL_SORT_END; + buffer_append(output, &type, sizeof(type)); + + return mask; +} + +static enum mail_sort_type +mail_sort_get_common_mask(const enum mail_sort_type *sort1, + const enum mail_sort_type *sort2, + unsigned int *count) +{ + enum mail_sort_type mask = 0; + + *count = 0; + while (*sort1 == *sort2 && *sort1 != MAIL_SORT_END) { + if (*sort1 != MAIL_SORT_REVERSE) + mask |= *sort1; + sort1++; sort2++; (*count)++; + } + + return mask; +} + +static enum mail_fetch_field +init_sort_elements(struct sort_context *ctx, + const char *wanted_headers[MAX_WANTED_HEADERS]) +{ + unsigned int i; + enum mail_fetch_field fields; + + /* figure out what data we'd like to cache */ + ctx->sort_element_size = sizeof(unsigned int); + ctx->cache_mask = 0; + + for (i = 0; ctx->sort_program[i] != MAIL_SORT_END; i++) { + enum mail_sort_type type = ctx->sort_program[i]; + + if (IS_SORT_STRING(type)) { + ctx->sort_element_size += sizeof(const char *); + + /* cache the second rule as well, if available */ + if (ctx->cache_mask != 0) { + ctx->cache_mask |= type; + break; + } + ctx->cache_mask |= type; + } else if (IS_SORT_TIME(type)) { + ctx->sort_element_size += sizeof(time_t); + ctx->cache_mask |= type; + break; + } else if (type == MAIL_SORT_SIZE) { + ctx->sort_element_size += sizeof(uoff_t); + ctx->cache_mask |= type; + break; + } + } + + fields = 0; + if (ctx->cache_mask & MAIL_SORT_ARRIVAL) + fields |= MAIL_FETCH_RECEIVED_DATE; + if (ctx->cache_mask & MAIL_SORT_DATE) + fields |= MAIL_FETCH_DATE; + if (ctx->cache_mask & MAIL_SORT_SIZE) + fields |= MAIL_FETCH_SIZE; + + /* @UNSAFE */ + i_assert(MAX_WANTED_HEADERS > 4); + i = 0; + if (ctx->cache_mask & MAIL_SORT_CC) + wanted_headers[i++] = "cc"; + if (ctx->cache_mask & MAIL_SORT_FROM) + wanted_headers[i++] = "from"; + if (ctx->cache_mask & MAIL_SORT_TO) + wanted_headers[i++] = "to"; + if (ctx->cache_mask & MAIL_SORT_SUBJECT) + wanted_headers[i++] = "subject"; + wanted_headers[i] = NULL; + + if ((ctx->cache_mask & MAIL_SORT_CC) || + (ctx->cache_mask & MAIL_SORT_FROM) || + (ctx->cache_mask & MAIL_SORT_TO) || + (ctx->cache_mask & MAIL_SORT_SUBJECT)) { + ctx->str_pool = pool_alloconly_create("sort str", 8192); + ctx->string_table = hash_create(default_pool, ctx->str_pool, + 0, str_hash, + (hash_cmp_callback_t)strcmp); + } + + return fields; +} + +static void mail_sort_deinit(struct sort_context *ctx) +{ + mail_sort_flush(ctx); + + if (ctx->string_table != NULL) + hash_destroy(ctx->string_table); + if (ctx->str_pool != NULL) + pool_unref(ctx->str_pool); + buffer_free(ctx->sort_buffer); + pool_unref(ctx->temp_pool); + + i_free(ctx->last_cc); + i_free(ctx->last_from); + i_free(ctx->last_subject); + i_free(ctx->last_to); +} + +int imap_sort(struct client *client, const char *charset, + struct mail_search_arg *args, + const enum mail_sort_type *sort_program) +{ + enum mail_sort_type norm_prog[MAX_SORT_PROGRAM_SIZE]; + enum mail_fetch_field wanted_fields; + const char *wanted_headers[MAX_WANTED_HEADERS]; + struct sort_context *ctx; + struct mail *mail; + buffer_t *buf; + unsigned int count; + int ret; + + ctx = t_new(struct sort_context, 1); + + /* normalize sorting program */ + buf = buffer_create_data(data_stack_pool, norm_prog, sizeof(norm_prog)); + mail_sort_normalize(sort_program, buf); + memcpy(ctx->sort_program, norm_prog, sizeof(ctx->sort_program)); + + /* remove the common part from sort program, we already know input is + sorted that much so we don't have to worry about it. */ + if (!client->mailbox->search_get_sorting(client->mailbox, norm_prog)) + return FALSE; + ctx->common_mask = mail_sort_get_common_mask(ctx->sort_program, + norm_prog, &count); + if (count > 0) { + memmove(ctx->sort_program, ctx->sort_program + count, + sizeof(ctx->sort_program) - + sizeof(ctx->sort_program[0]) * count); + } + + memset(wanted_headers, 0, sizeof(wanted_headers)); + wanted_fields = init_sort_elements(ctx, wanted_headers); + + /* initialize searching */ + ctx->search_ctx = client->mailbox-> + search_init(client->mailbox, charset, args, norm_prog, + wanted_fields, wanted_headers); + if (ctx->search_ctx == NULL) + return FALSE; + + ctx->box = client->mailbox; + ctx->output = client->output; + ctx->temp_pool = pool_alloconly_create("sort temp", 8192); + ctx->sort_buffer = buffer_create_dynamic(system_pool, + 128 * ctx->sort_element_size, + (size_t)-1); + + ctx->str = t_str_new(STRBUF_SIZE); + str_append(ctx->str, "* SORT"); + + ctx->id_is_uid = client->cmd_uid; + + while ((mail = client->mailbox->search_next(ctx->search_ctx)) != NULL) + mail_sort_input(ctx, mail); + + mail_sort_flush(ctx); + ret = client->mailbox->search_deinit(ctx->search_ctx); + + if (ctx->written || ret) { + str_append(ctx->str, "\r\n"); + o_stream_send(client->output, str_data(ctx->str), + str_len(ctx->str)); + } + + mail_sort_deinit(ctx); + return ret; +} + +static const char *string_table_get(struct sort_context *ctx, const char *str) +{ + char *value; + + if (str == NULL) + return NULL; + if (*str == '\0') + return ""; + + value = hash_lookup(ctx->string_table, str); + if (value == NULL) { + value = p_strdup(ctx->str_pool, str); + hash_insert(ctx->string_table, value, value); + } + + return value; +} + +static void mail_sort_check_flush(struct sort_context *ctx, struct mail *mail) +{ + const char *str; + time_t t; + uoff_t size; + int changed = FALSE; + + if (ctx->common_mask & MAIL_SORT_ARRIVAL) { + t = mail->get_received_date(mail); + if (t != ctx->last_arrival) { + ctx->last_arrival = t; + changed = TRUE; + } + } + + if (ctx->common_mask & MAIL_SORT_CC) { + str = mail->get_first_mailbox(mail, "cc"); + if (str != NULL) + str = str_ucase(t_strdup_noconst(str)); + + if (null_strcmp(str, ctx->last_cc) != 0) { + i_free(ctx->last_cc); + ctx->last_cc = i_strdup(str); + changed = TRUE; + } + } + + if (ctx->common_mask & MAIL_SORT_DATE) { + t = mail->get_date(mail, NULL); + if (t != ctx->last_date) { + ctx->last_date = t; + changed = TRUE; + } + } + + if (ctx->common_mask & MAIL_SORT_FROM) { + str = mail->get_first_mailbox(mail, "from"); + if (str != NULL) + str = str_ucase(t_strdup_noconst(str)); + + if (null_strcmp(str, ctx->last_from) != 0) { + i_free(ctx->last_from); + ctx->last_from = i_strdup(str); + changed = TRUE; + } + } + + if (ctx->common_mask & MAIL_SORT_SIZE) { + size = mail->get_size(mail); + if (size != ctx->last_size) { + ctx->last_size = size; + changed = TRUE; + } + } + + if (ctx->common_mask & MAIL_SORT_SUBJECT) { + str = mail->get_header(mail, "subject"); + if (str != NULL) { + p_clear(ctx->temp_pool); + str = imap_get_base_subject_cased(ctx->temp_pool, + str, NULL); + } + + if (null_strcmp(str, ctx->last_subject) != 0) { + i_free(ctx->last_subject); + ctx->last_subject = i_strdup(str); + changed = TRUE; + } + } + + if (ctx->common_mask & MAIL_SORT_TO) { + str = mail->get_first_mailbox(mail, "to"); + if (str != NULL) + str = str_ucase(t_strdup_noconst(str)); + + if (null_strcmp(str, ctx->last_to) != 0) { + i_free(ctx->last_to); + ctx->last_to = i_strdup(str); + changed = TRUE; + } + } + + if (changed) + mail_sort_flush(ctx); +} + +static void mail_sort_input(struct sort_context *ctx, struct mail *mail) +{ + /* @UNSAFE */ + unsigned char *buf; + unsigned int id; + time_t t; + uoff_t size; + const char *str; + size_t pos; + + t_push(); + if (ctx->common_mask != 0) + mail_sort_check_flush(ctx, mail); + + buf = buffer_append_space(ctx->sort_buffer, ctx->sort_element_size); + id = ctx->id_is_uid ? mail->uid : mail->seq; + memcpy(buf, &id, sizeof(id)); pos = sizeof(id); + + if (ctx->cache_mask & MAIL_SORT_ARRIVAL) { + if (ctx->common_mask & MAIL_SORT_ARRIVAL) + t = ctx->last_arrival; + else + t = mail->get_received_date(mail); + memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t); + } + + if (ctx->cache_mask & MAIL_SORT_DATE) { + if (ctx->common_mask & MAIL_SORT_DATE) + t = ctx->last_date; + else + t = mail->get_date(mail, NULL); + memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t); + } + + if (ctx->cache_mask & MAIL_SORT_SIZE) { + if (ctx->common_mask & MAIL_SORT_SIZE) + size = ctx->last_size; + else + size = mail->get_size(mail); + + memcpy(buf + pos, &size, sizeof(size)); pos += sizeof(size); + } + + if (ctx->cache_mask & MAIL_SORT_CC) { + if (ctx->common_mask & MAIL_SORT_CC) + str = ctx->last_cc; + else { + str = mail->get_first_mailbox(mail, "cc"); + if (str != NULL) + str = str_ucase(t_strdup_noconst(str)); + } + str = string_table_get(ctx, str); + + memcpy(buf + pos, &str, sizeof(const char *)); + pos += sizeof(const char *); + } + + if (ctx->cache_mask & MAIL_SORT_FROM) { + if (ctx->common_mask & MAIL_SORT_FROM) + str = ctx->last_from; + else { + str = mail->get_first_mailbox(mail, "from"); + if (str != NULL) + str = str_ucase(t_strdup_noconst(str)); + } + str = string_table_get(ctx, str); + + memcpy(buf + pos, &str, sizeof(const char *)); + pos += sizeof(const char *); + } + + if (ctx->cache_mask & MAIL_SORT_TO) { + if (ctx->common_mask & MAIL_SORT_TO) + str = ctx->last_to; + else { + str = mail->get_first_mailbox(mail, "to"); + if (str != NULL) + str = str_ucase(t_strdup_noconst(str)); + } + str = string_table_get(ctx, str); + + memcpy(buf + pos, &str, sizeof(const char *)); + pos += sizeof(const char *); + } + + if (ctx->cache_mask & MAIL_SORT_SUBJECT) { + if (ctx->common_mask & MAIL_SORT_SUBJECT) + str = ctx->last_subject; + else { + str = mail->get_header(mail, "subject"); + + if (str != NULL) { + p_clear(ctx->temp_pool); + str = imap_get_base_subject_cased( + ctx->temp_pool, str, NULL); + } + } + str = string_table_get(ctx, str); + + memcpy(buf + pos, &str, sizeof(const char *)); + pos += sizeof(const char *); + } + + i_assert(pos == ctx->sort_element_size); + + t_pop(); +} + +static struct sort_context *qsort_context; + +static struct mail *get_mail(struct sort_context *ctx, const unsigned char *buf) +{ + unsigned int id = *((unsigned int *) buf); + + if (ctx->id_is_uid) + return ctx->box->fetch_uid(ctx->box, id, 0); + else + return ctx->box->fetch_seq(ctx->box, id, 0); + +} + +static time_t get_time(enum mail_sort_type type, const unsigned char *buf, + struct sort_context *ctx) +{ + time_t t; + + if ((ctx->cache_mask & type) == 0) { + struct mail *mail = get_mail(ctx, buf); + + if (mail == NULL) + return 0; + + switch (type) { + case MAIL_SORT_ARRIVAL: + return mail->get_received_date(mail); + case MAIL_SORT_DATE: + t = mail->get_date(mail, NULL); + if (t == (time_t)-1) + t = 0; + return t; + default: + i_unreached(); + return 0; + } + } + + /* use memcpy() to avoid any alignment problems */ + memcpy(&t, buf + sizeof(unsigned int), sizeof(t)); + return t; +} + +static time_t get_uofft(enum mail_sort_type type, const unsigned char *buf, + struct sort_context *ctx) +{ + uoff_t size; + + if ((ctx->cache_mask & type) == 0) { + struct mail *mail = get_mail(ctx, buf); + + if (mail == NULL) + return 0; + + i_assert(type == MAIL_SORT_SIZE); + + return mail->get_size(mail); + } + + /* use memcpy() to avoid any alignment problems */ + memcpy(&size, buf + sizeof(unsigned int), sizeof(size)); + return size; +} + +static const char *get_str(enum mail_sort_type type, const unsigned char *buf, + struct sort_context *ctx) +{ + const char *str; + enum mail_sort_type type2; + int pos; + + if ((ctx->cache_mask & type) == 0) { + struct mail *mail = get_mail(ctx, buf); + + if (mail == NULL) + return NULL; + + switch (type) { + case MAIL_SORT_SUBJECT: + str = mail->get_header(mail, "subject"); + if (str == NULL) + return NULL; + + p_clear(ctx->temp_pool); + return imap_get_base_subject_cased(ctx->temp_pool, + str, NULL); + case MAIL_SORT_CC: + str = mail->get_first_mailbox(mail, "cc"); + break; + case MAIL_SORT_FROM: + str = mail->get_first_mailbox(mail, "from"); + break; + case MAIL_SORT_TO: + str = mail->get_first_mailbox(mail, "to"); + break; + default: + i_unreached(); + } + + if (str != NULL) + str = str_ucase(t_strdup_noconst(str)); + return str; + } + + /* figure out where it is. pretty ugly. */ + type2 = (ctx->cache_mask & ~type); + + if (type2 == 0) + pos = 0; + else if (IS_SORT_TIME(type2)) + pos = sizeof(time_t); + else if (type2 == MAIL_SORT_SIZE) + pos = sizeof(uoff_t); + else { + if (type == MAIL_SORT_SUBJECT) + pos = sizeof(const char *); + else if (type2 != MAIL_SORT_SUBJECT && type > type2) + pos = sizeof(const char *); + else + pos = 0; + } + + /* use memcpy() to avoid any alignment problems */ + memcpy(&str, buf + pos + sizeof(unsigned int), sizeof(const char *)); + return str; +} + +static int mail_sort_qsort_func(const void *p1, const void *p2) +{ + enum mail_sort_type *sorting; + int ret, reverse = FALSE; + + sorting = qsort_context->sort_program; + + t_push(); + + ret = 0; + for (; *sorting != MAIL_SORT_END && ret == 0; sorting++) { + if (*sorting == MAIL_SORT_REVERSE) { + reverse = !reverse; + continue; + } + + switch (*sorting) { + case MAIL_SORT_ARRIVAL: + case MAIL_SORT_DATE: { + time_t r1, r2; + + r1 = get_time(*sorting, p1, qsort_context); + r2 = get_time(*sorting, p2, qsort_context); + ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0; + break; + } + case MAIL_SORT_SIZE: { + uoff_t r1, r2; + + r1 = get_uofft(*sorting, p1, qsort_context); + r2 = get_uofft(*sorting, p2, qsort_context); + ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0; + break; + } + case MAIL_SORT_CC: + case MAIL_SORT_FROM: + case MAIL_SORT_TO: + case MAIL_SORT_SUBJECT: + ret = null_strcmp(get_str(*sorting, p1, qsort_context), + get_str(*sorting, p2, qsort_context)); + break; + default: + i_unreached(); + } + + if (reverse) { + if (ret > 0) + ret = -1; + else if (ret < 0) + ret = 1; + } + + reverse = FALSE; + } + + t_pop(); + + return ret != 0 ? ret : + (*((unsigned int *) p1) < *((unsigned int *) p2) ? -1 : 1); +} + +static void mail_sort_flush(struct sort_context *ctx) +{ + unsigned char *arr; + size_t i, count; + + qsort_context = ctx; + + arr = buffer_get_modifyable_data(ctx->sort_buffer, NULL); + count = buffer_get_used_size(ctx->sort_buffer) / ctx->sort_element_size; + if (count == 0) + return; + + qsort(arr, count, ctx->sort_element_size, mail_sort_qsort_func); + + for (i = 0; i < count; i++, arr += ctx->sort_element_size) { + if (str_len(ctx->str) >= STRBUF_SIZE-MAX_INT_STRLEN) { + /* flush */ + o_stream_send(ctx->output, + str_data(ctx->str), str_len(ctx->str)); + str_truncate(ctx->str, 0); + ctx->written = TRUE; + } + + str_printfa(ctx->str, " %u", *((unsigned int *) arr)); + } + + buffer_set_used_size(ctx->sort_buffer, 0); + + if (ctx->string_table != NULL) { + hash_clear(ctx->string_table, TRUE); + p_clear(ctx->str_pool); + } +} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-sort.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-sort.h Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,7 @@ +#ifndef __IMAP_SORT_H +#define __IMAP_SORT_H + +int imap_sort(struct client *client, const char *charset, + struct mail_search_arg *args, enum mail_sort_type *sorting); + +#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-thread.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-thread.c Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,950 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +/* + * Merge sort code in sort_nodes() is copyright 2001 Simon Tatham. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Implementation of draft-ietf-imapext-thread-12 threading algorithm */ + +#include "common.h" +#include "hash.h" +#include "ostream.h" +#include "str.h" +#include "message-tokenize.h" +#include "imap-base-subject.h" +#include "imap-thread.h" + +#include + +/* how much memory to allocate initially. these are very rough + approximations. */ +#define APPROX_MSG_COUNT 128 +#define APPROX_MSGID_SIZE 45 + +/* Try to buffer this much data before sending it to output stream. */ +#define OUTPUT_BUF_SIZE 2048 + +#define NODE_IS_DUMMY(node) ((node)->id == 0) +#define NODE_HAS_PARENT(ctx, node) \ + ((node)->parent != NULL && (node)->parent != &(ctx)->root_node) + +struct root_info { + char *base_subject; + unsigned int reply:1; + unsigned int sorted:1; +}; + +struct node { + struct node *parent, *first_child, *next; + + unsigned int id; + time_t sent_date; + + union { + char *msgid; + struct root_info *info; + } u; +}; + +struct thread_context { + struct mail_search_context *search_ctx; + struct mailbox *box; + struct ostream *output; + + pool_t pool; + pool_t temp_pool; + + struct hash_table *msgid_hash; + struct hash_table *subject_hash; + + struct node root_node; + size_t root_count; /* not exact after prune_dummy_messages() */ + + int id_is_uid; +}; + +static void mail_thread_input(struct thread_context *ctx, struct mail *mail); +static void mail_thread_finish(struct thread_context *ctx); + +static void mail_thread_deinit(struct thread_context *ctx) +{ + if (ctx->msgid_hash != NULL) + hash_destroy(ctx->msgid_hash); + if (ctx->subject_hash != NULL) + hash_destroy(ctx->subject_hash); + + pool_unref(ctx->temp_pool); + pool_unref(ctx->pool); +} + +int imap_thread(struct client *client, const char *charset, + struct mail_search_arg *args, enum mail_thread_type type) +{ + static const char *wanted_headers[] = { + "message-id", "in-reply-to", "references", + NULL + }; + struct thread_context *ctx; + struct mail *mail; + int ret; + + if (type != MAIL_THREAD_REFERENCES) + i_fatal("Only REFERENCES threading supported"); + + ctx = t_new(struct thread_context, 1); + + /* initialize searching */ + ctx->search_ctx = client->mailbox-> + search_init(client->mailbox, charset, args, NULL, + MAIL_FETCH_DATE, wanted_headers); + if (ctx->search_ctx == NULL) + return FALSE; + + ctx->box = client->mailbox; + ctx->output = client->output; + ctx->pool = pool_alloconly_create("thread_context", + sizeof(struct node) * + APPROX_MSG_COUNT); + ctx->temp_pool = pool_alloconly_create("thread_context temp", + APPROX_MSG_COUNT * + APPROX_MSGID_SIZE); + ctx->msgid_hash = hash_create(default_pool, ctx->temp_pool, + APPROX_MSG_COUNT*2, str_hash, + (hash_cmp_callback_t)strcmp); + + ctx->id_is_uid = client->cmd_uid; + while ((mail = client->mailbox->search_next(ctx->search_ctx)) != NULL) + mail_thread_input(ctx, mail); + + o_stream_send_str(client->output, "* THREAD"); + mail_thread_finish(ctx); + o_stream_send_str(client->output, "\r\n"); + + ret = client->mailbox->search_deinit(ctx->search_ctx); + mail_thread_deinit(ctx); + return ret; +} + +static void add_root(struct thread_context *ctx, struct node *node) +{ + node->parent = &ctx->root_node; + node->next = ctx->root_node.first_child; + ctx->root_node.first_child = node; + + ctx->root_count++; +} + +static struct node *create_node(struct thread_context *ctx, const char *msgid) +{ + struct node *node; + + node = p_new(ctx->pool, struct node, 1); + node->u.msgid = p_strdup(ctx->temp_pool, msgid); + + hash_insert(ctx->msgid_hash, node->u.msgid, node); + return node; +} + +static struct node *create_id_node(struct thread_context *ctx, + unsigned int id, time_t sent_date) +{ + struct node *node; + + node = p_new(ctx->pool, struct node, 1); + node->id = id; + node->sent_date = sent_date; + + add_root(ctx, node); + return node; +} + +static struct node *update_message(struct thread_context *ctx, + const char *msgid, time_t sent_date, + unsigned int id) +{ + struct node *node; + + if (msgid == NULL) + return create_id_node(ctx, id, sent_date); + + node = hash_lookup(ctx->msgid_hash, msgid); + if (node == NULL) { + /* first time we see this message */ + node = create_node(ctx, msgid); + node->id = id; + node->sent_date = sent_date; + return node; + } + + if (node->id == 0) { + /* seen before in references */ + node->id = id; + node->sent_date = sent_date; + } else { + /* duplicate */ + node = create_id_node(ctx, id, sent_date); + } + + return node; +} + +static int get_untokenized_msgid(const char **msgid_p, string_t *msgid) +{ + static const enum message_token stop_tokens[] = { '>', TOKEN_LAST }; + struct message_tokenizer *tok; + int valid_end; + + tok = message_tokenize_init((const unsigned char *) *msgid_p, + (size_t)-1, NULL, NULL); + message_tokenize_dot_token(tok, FALSE); /* just a minor speedup */ + + message_tokenize_get_string(tok, msgid, NULL, stop_tokens); + valid_end = message_tokenize_get(tok) == '>'; + + *msgid_p += message_tokenize_get_parse_position(tok); + message_tokenize_deinit(tok); + + if (valid_end) { + if (strchr(str_c(msgid), '@') != NULL) { + /* - valid message ID found */ + return TRUE; + } + } + + return FALSE; +} + +static void strip_lwsp(char *str) +{ + /* @UNSAFE */ + char *dest; + + /* find the first lwsp */ + while (*str != ' ' && *str != '\t' && *str != '\r' && *str != '\n') { + if (*str == '\0') + return; + str++; + } + + for (dest = str; *str != '\0'; str++) { + if (*str != ' ' && *str != '\t' && *str != '\r' && *str != '\n') + *dest++ = *str; + } + *dest = '\0'; +} + +static const char *get_msgid(const char **msgid_p) +{ + const char *msgid = *msgid_p; + const char *p; + string_t *str = NULL; + int found_at; + + if (*msgid_p == NULL) + return NULL; + + for (;;) { + /* skip until '<' */ + while (*msgid != '<') { + if (*msgid == '\0') { + *msgid_p = msgid; + return NULL; + } + msgid++; + } + msgid++; + + /* check it through quickly to see if it's already normalized */ + p = msgid; found_at = FALSE; + for (;; p++) { + if ((unsigned char)*p >= 'A') /* matches most */ + continue; + + if (*p == '@') + found_at = TRUE; + if (*p == '>' || *p == '"' || *p == '(') + break; + + if (*p == '\0') { + *msgid_p = p; + return NULL; + } + } + + if (*p == '>') { + *msgid_p = p+1; + if (found_at) { + char *s; + + s = p_strdup_until(data_stack_pool, msgid, p); + strip_lwsp(s); + return s; + } + } else { + /* ok, do it the slow way */ + *msgid_p = msgid; + + if (str == NULL) { + /* allocate only once, so we don't leak + with multiple invalid message IDs */ + str = t_str_new(256); + } + if (get_untokenized_msgid(msgid_p, str)) + return str_c(str); + } + + /* invalid message id, see if there's another valid one */ + msgid = *msgid_p; + } +} + +static void unlink_child(struct thread_context *ctx, + struct node *child, int add_to_root) +{ + struct node **node; + + node = &child->parent->first_child; + for (; *node != NULL; node = &(*node)->next) { + if (*node == child) { + *node = child->next; + break; + } + } + + child->next = NULL; + if (!add_to_root) + child->parent = NULL; + else + add_root(ctx, child); +} + +static int find_child(struct node *node, struct node *child) +{ + do { + if (node == child) + return TRUE; + + if (node->first_child != NULL) { + if (find_child(node->first_child, child)) + return TRUE; + } + + node = node->next; + } while (node != NULL); + + return FALSE; +} + +static void link_node(struct thread_context *ctx, const char *parent_msgid, + struct node *child, int replace) +{ + struct node *parent, **node; + + if (NODE_HAS_PARENT(ctx, child) && !replace) { + /* already got a parent, don't want to replace it */ + return; + } + + parent = hash_lookup(ctx->msgid_hash, parent_msgid); + if (parent == NULL) + parent = create_node(ctx, parent_msgid); + + if (child->parent == parent) { + /* already have this parent, ignore */ + return; + } + + if (find_child(child, parent)) { + /* this would create a loop, not allowed */ + return; + } + + if (child->parent != NULL) + unlink_child(ctx, child, FALSE); + + /* link them */ + child->parent = parent; + + node = &parent->first_child; + while (*node != NULL) + node = &(*node)->next; + *node = child; +} + +static void link_message(struct thread_context *ctx, + const char *parent_msgid, const char *child_msgid, + int replace) +{ + struct node *child; + + child = hash_lookup(ctx->msgid_hash, child_msgid); + if (child == NULL) + child = create_node(ctx, child_msgid); + + link_node(ctx, parent_msgid, child, replace); +} + +static int link_references(struct thread_context *ctx, + struct node *node, const char *references) +{ + const char *parent_id, *child_id; + + parent_id = get_msgid(&references); + if (parent_id == NULL) + return FALSE; + + while ((child_id = get_msgid(&references)) != NULL) { + link_message(ctx, parent_id, child_id, FALSE); + parent_id = child_id; + } + + /* link the last message to us */ + link_node(ctx, parent_id, node, TRUE); + return TRUE; +} + +static void mail_thread_input(struct thread_context *ctx, struct mail *mail) +{ + const char *refid, *message_id, *in_reply_to, *references; + struct node *node; + time_t sent_date; + + t_push(); + + sent_date = mail->get_date(mail, NULL); + if (sent_date == (time_t)-1) + sent_date = 0; + + message_id = mail->get_header(mail, "message-id"); + node = update_message(ctx, get_msgid(&message_id), sent_date, + ctx->id_is_uid ? mail->uid : mail->seq); + + /* link references */ + references = mail->get_header(mail, "references"); + if (!link_references(ctx, node, references)) { + in_reply_to = mail->get_header(mail, "in-reply-to"); + refid = in_reply_to == NULL ? NULL : get_msgid(&in_reply_to); + + if (refid != NULL) + link_node(ctx, refid, node, TRUE); + else { + /* no references, make sure it's not linked */ + if (node != NULL && NODE_HAS_PARENT(ctx, node)) + unlink_child(ctx, node, TRUE); + } + } + + t_pop(); +} + +static struct node *find_last_child(struct node *node) +{ + node = node->first_child; + while (node->next != NULL) + node = node->next; + + return node; +} + +static struct node **promote_children(struct node **parent) +{ + struct node *new_parent, *old_parent, *child; + + old_parent = *parent; + new_parent = old_parent->parent; + + child = old_parent->first_child; + *parent = child; + + for (;;) { + child->parent = new_parent; + if (child->next == NULL) + break; + child = child->next; + } + + child->next = old_parent->next; + return &child->next; +} + +static void prune_dummy_messages(struct thread_context *ctx, + struct node **node_p) +{ + struct node **a; + + a = node_p; + while (*node_p != NULL) { + if ((*node_p)->first_child != NULL) + prune_dummy_messages(ctx, &(*node_p)->first_child); + + if (NODE_IS_DUMMY(*node_p)) { + if ((*node_p)->first_child == NULL) { + /* no children -> delete */ + *node_p = (*node_p)->next; + continue; + } else if (NODE_HAS_PARENT(ctx, *node_p) || + (*node_p)->first_child->next == NULL) { + /* promote children to our level, + deleting the dummy node */ + node_p = promote_children(node_p); + continue; + } + } + + node_p = &(*node_p)->next; + } +} + +static int node_cmp(struct node *a, struct node *b) +{ + time_t date_a, date_b; + unsigned int id_a, id_b; + + date_a = a->id != 0 ? a->sent_date : a->first_child->sent_date; + date_b = b->id != 0 ? b->sent_date : b->first_child->sent_date; + + if (date_a != date_b && date_a != 0 && date_b != 0) + return date_a < date_b ? -1 : 1; + + id_a = a->id != 0 ? a->id : a->first_child->id; + id_b = b->id != 0 ? b->id : b->first_child->id; + return id_a < id_b ? -1 : 1; +} + +static struct node *sort_nodes(struct node *list) +{ + struct node *p, *q, *e, *tail; + size_t insize, nmerges, psize, qsize, i; + + i_assert(list != NULL); + + if (list->next == NULL) + return list; /* just one node */ + + insize = 1; + + for (;;) { + p = list; + list = NULL; + tail = NULL; + + nmerges = 0; /* count number of merges we do in this pass */ + while (p != 0) { + nmerges++; /* there exists a merge to be done */ + + /* step `insize' places along from p */ + q = p; + psize = 0; + for (i = 0; i < insize; i++) { + psize++; + q = q->next; + if (q == NULL) break; + } + + /* if q hasn't fallen off end, we have two lists to + merge */ + qsize = insize; + + /* now we have two lists; merge them */ + while (psize > 0 || (qsize > 0 && q != NULL)) { + /* decide whether next element of merge comes + from p or q */ + if (psize == 0) { + /* p is empty; e must come from q. */ + e = q; q = q->next; qsize--; + } else if (qsize == 0 || !q) { + /* q is empty; e must come from p. */ + e = p; p = p->next; psize--; + } else if (node_cmp(p, q) <= 0) { + /* First element of p is lower + (or same); e must come from p. */ + e = p; p = p->next; psize--; + } else { + /* First element of q is lower; + e must come from q. */ + e = q; q = q->next; qsize--; + } + + /* add the next element to the merged list */ + if (tail) + tail->next = e; + else + list = e; + tail = e; + } + + /* now p has stepped `insize' places along, + and q has too */ + p = q; + } + tail->next = NULL; + + /* If we have done only one merge, we're finished. */ + if (nmerges <= 1) { + /* allow for nmerges == 0, the empty list case */ + return list; + } + + /* Otherwise repeat, merging lists twice the size */ + insize *= 2; + } +} + +static void add_base_subject(struct thread_context *ctx, + const char *subject, struct node *node) +{ + struct node *hash_node; + char *hash_subject; + void *key, *value; + int is_reply_or_forward; + + if (subject == NULL) + return; + + subject = imap_get_base_subject_cased(data_stack_pool, subject, + &is_reply_or_forward); + if (*subject == '\0') + return; + + if (!hash_lookup_full(ctx->subject_hash, subject, &key, &value)) { + hash_subject = p_strdup(ctx->temp_pool, subject); + hash_insert(ctx->subject_hash, hash_subject, node); + } else { + hash_subject = key; + hash_node = value; + + if (!NODE_IS_DUMMY(hash_node) && + (NODE_IS_DUMMY(node) || + (hash_node->u.info->reply && !is_reply_or_forward))) + hash_update(ctx->subject_hash, hash_subject, node); + } + + node->u.info->base_subject = hash_subject; + node->u.info->reply = is_reply_or_forward; +} + +static void gather_base_subjects(struct thread_context *ctx) +{ + struct mail *mail; + struct node *node; + unsigned int id; + + ctx->subject_hash = + hash_create(default_pool, ctx->temp_pool, ctx->root_count * 2, + str_hash, (hash_cmp_callback_t)strcmp); + + node = ctx->root_node.first_child; + for (; node != NULL; node = node->next) { + if (!NODE_IS_DUMMY(node)) + id = node->id; + else { + /* sort children, use the first one's id */ + node->first_child = sort_nodes(node->first_child); + id = node->first_child->id; + + node->u.info->sorted = TRUE; + } + + if (ctx->id_is_uid) + mail = ctx->box->fetch_uid(ctx->box, id, 0); + else + mail = ctx->box->fetch_seq(ctx->box, id, 0); + + if (mail != NULL) { + t_push(); + add_base_subject(ctx, mail->get_header(mail, "subject"), + node); + t_pop(); + } + } +} + +static void reset_children_parent(struct node *parent) +{ + struct node *node; + + for (node = parent->first_child; node != NULL; node = node->next) + node->parent = parent; +} + +static void merge_subject_threads(struct thread_context *ctx) +{ + struct node **node_p, *node, *hash_node; + char *base_subject; + + for (node_p = &ctx->root_node.first_child; *node_p != NULL; ) { + node = *node_p; + + if (node->u.info == NULL) { + /* deleted node */ + *node_p = node->next; + continue; + } + + /* (ii) If the thread subject is empty, skip this message. */ + base_subject = node->u.info->base_subject; + if (base_subject == NULL) { + node_p = &node->next; + continue; + } + + /* (iii) Lookup the message associated with this thread + subject in the subject table. */ + hash_node = hash_lookup(ctx->subject_hash, base_subject); + i_assert(hash_node != NULL); + + /* (iv) If the message in the subject table is the current + message, skip this message. */ + if (hash_node == node) { + node_p = &node->next; + continue; + } + + /* Otherwise, merge the current message with the one in the + subject table using the following rules: */ + + if (NODE_IS_DUMMY(node) && + NODE_IS_DUMMY(hash_node)) { + /* If both messages are dummies, append the current + message's children to the children of the message in + the subject table (the children of both messages + become siblings), and then delete the current + message. */ + find_last_child(hash_node)->next = node->first_child; + + *node_p = node->next; + hash_node->u.info->sorted = FALSE; + } else if (NODE_IS_DUMMY(hash_node) || + (node->u.info->reply && !hash_node->u.info->reply)) { + /* If the message in the subject table is a dummy + and the current message is not, make the current + message a child of the message in the subject table + (a sibling of its children). + + If the current message is a reply or forward and + the message in the subject table is not, make the + current message a child of the message in the + subject table (a sibling of its children). */ + *node_p = node->next; + + node->parent = hash_node; + node->next = hash_node->first_child; + hash_node->first_child = node; + + hash_node->u.info->sorted = FALSE; + } else { + /* Otherwise, create a new dummy message and make both + the current message and the message in the subject + table children of the dummy. Then replace the + message in the subject table with the dummy + message. */ + + /* create new nodes for the children - reusing + existing ones have problems since the other one + might have been handled already and we'd introduce + loops.. + + current node will be destroyed, hash_node will be + the dummy so we don't need to update hash */ + struct node *node1, *node2; + + node1 = p_new(ctx->pool, struct node, 1); + node2 = p_new(ctx->pool, struct node, 1); + + memcpy(node1, node, sizeof(struct node)); + memcpy(node2, hash_node, sizeof(struct node)); + + node1->parent = hash_node; + node2->parent = hash_node; + node1->next = node2; + node2->next = NULL; + + reset_children_parent(node1); + reset_children_parent(node2); + + hash_node->id = 0; + hash_node->first_child = node1; + hash_node->u.info->reply = FALSE; + hash_node->u.info->sorted = FALSE; + + node->first_child = NULL; + node->u.info = NULL; + *node_p = node->next; + } + } +} + +static void sort_root_nodes(struct thread_context *ctx) +{ + struct node *node; + + /* sort the children first, they're needed to sort dummy root nodes */ + node = ctx->root_node.first_child; + for (; node != NULL; node = node->next) { + if (node->u.info == NULL) + continue; + + if (NODE_IS_DUMMY(node) && !node->u.info->sorted && + node->first_child != NULL) + node->first_child = sort_nodes(node->first_child); + } + + ctx->root_node.first_child = sort_nodes(ctx->root_node.first_child); +} + +static int send_nodes(struct thread_context *ctx, + string_t *str, struct node *node) +{ + if (node->next == NULL && NODE_HAS_PARENT(ctx, node)) { + /* no siblings - special case to avoid extra paranthesis */ + if (node->first_child == NULL) + str_printfa(str, "%u", node->id); + else { + str_printfa(str, "%u ", node->id); + send_nodes(ctx, str, sort_nodes(node->first_child)); + } + return TRUE; + } + + while (node != NULL) { + if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) { + /* string getting full, flush it */ + if (!o_stream_send(ctx->output, + str_data(str), str_len(str))) + return FALSE; + str_truncate(str, 0); + } + + if (node->first_child == NULL) + str_printfa(str, "(%u)", node->id); + else { + str_printfa(str, "(%u ", node->id); + send_nodes(ctx, str, sort_nodes(node->first_child)); + str_append_c(str, ')'); + } + + node = node->next; + } + return TRUE; +} + +static void send_roots(struct thread_context *ctx) +{ + struct node *node; + string_t *str; + + str = t_str_new(OUTPUT_BUF_SIZE); + str_append_c(str, ' '); + + /* sort root nodes again, they have been modified since the last time */ + sort_root_nodes(ctx); + + node = ctx->root_node.first_child; + for (; node != NULL; node = node->next) { + if (node->u.info == NULL) + continue; + + if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) { + /* string getting full, flush it */ + if (!o_stream_send(ctx->output, + str_data(str), str_len(str))) + return; + str_truncate(str, 0); + } + + str_append_c(str, '('); + if (!NODE_IS_DUMMY(node)) + str_printfa(str, "%u", node->id); + + if (node->first_child != NULL) { + if (!NODE_IS_DUMMY(node)) + str_append_c(str, ' '); + + if (!node->u.info->sorted) { + node->first_child = + sort_nodes(node->first_child); + } + + if (!send_nodes(ctx, str, node->first_child)) + return; + } + + str_append_c(str, ')'); + } + + (void)o_stream_send(ctx->output, str_data(str), str_len(str)); +} + +static void save_root_cb(void *key __attr_unused__, void *value, void *context) +{ + struct thread_context *ctx = context; + struct node *node = value; + + if (node->parent == NULL) + add_root(ctx, node); +} + +static void mail_thread_finish(struct thread_context *ctx) +{ + struct node *node; + + /* (2) save root nodes and drop the msgids */ + hash_foreach(ctx->msgid_hash, save_root_cb, ctx); + + /* drop the memory allocated for message-IDs and msgid_hash, + reuse their memory for base subjects */ + hash_destroy(ctx->msgid_hash); + ctx->msgid_hash = NULL; + + p_clear(ctx->temp_pool); + + if (ctx->root_node.first_child == NULL) { + /* no messages */ + mail_thread_deinit(ctx); + return; + } + + /* (3) */ + prune_dummy_messages(ctx, &ctx->root_node.first_child); + + /* initialize the node->u.info for all root nodes */ + node = ctx->root_node.first_child; + for (; node != NULL; node = node->next) + node->u.info = p_new(ctx->pool, struct root_info, 1); + + /* (4) */ + sort_root_nodes(ctx); + + /* (5) Gather together messages under the root that have the same + base subject text. */ + gather_base_subjects(ctx); + + /* (5.C) Merge threads with the same thread subject. */ + merge_subject_threads(ctx); + + /* (6) Sort and send replies */ + t_push(); + send_roots(ctx); + t_pop(); +} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/imap/imap-thread.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap/imap-thread.h Mon Jan 20 16:52:51 2003 +0200 @@ -0,0 +1,7 @@ +#ifndef __IMAP_THREAD_H +#define __IMAP_THREAD_H + +int imap_thread(struct client *client, const char *charset, + struct mail_search_arg *args, enum mail_thread_type type); + +#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-imap/Makefile.am --- a/src/lib-imap/Makefile.am Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-imap/Makefile.am Mon Jan 20 16:52:51 2003 +0200 @@ -11,7 +11,6 @@ imap-date.c \ imap-envelope.c \ imap-match.c \ - imap-message-cache.c \ imap-quote.c \ imap-parser.c \ imap-util.c @@ -22,7 +21,6 @@ imap-date.h \ imap-envelope.h \ imap-match.h \ - imap-message-cache.h \ imap-quote.h \ imap-parser.h \ imap-util.h diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-imap/imap-envelope.c --- a/src/lib-imap/imap-envelope.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-imap/imap-envelope.c Mon Jan 20 16:52:51 2003 +0200 @@ -35,25 +35,31 @@ switch (name_len) { case 2: - if (memcasecmp(name, "To", 2) == 0 && d->to == NULL) - d->to = message_address_parse(pool, value, value_len); - else if (memcasecmp(name, "Cc", 2) == 0 && d->cc == NULL) - d->cc = message_address_parse(pool, value, value_len); + if (memcasecmp(name, "To", 2) == 0 && d->to == NULL) { + d->to = message_address_parse(pool, value, + value_len, 0); + } else if (memcasecmp(name, "Cc", 2) == 0 && d->cc == NULL) { + d->cc = message_address_parse(pool, value, + value_len, 0); + } break; case 3: - if (memcasecmp(name, "Bcc", 3) == 0 && d->bcc == NULL) - d->bcc = message_address_parse(pool, value, value_len); + if (memcasecmp(name, "Bcc", 3) == 0 && d->bcc == NULL) { + d->bcc = message_address_parse(pool, value, + value_len, 0); + } break; case 4: - if (memcasecmp(name, "From", 4) == 0 && d->from == NULL) - d->from = message_address_parse(pool, value, value_len); - else if (memcasecmp(name, "Date", 4) == 0 && d->date == NULL) + if (memcasecmp(name, "From", 4) == 0 && d->from == NULL) { + d->from = message_address_parse(pool, value, + value_len, 0); + } else if (memcasecmp(name, "Date", 4) == 0 && d->date == NULL) d->date = imap_quote_value(pool, value, value_len); break; case 6: if (memcasecmp(name, "Sender", 6) == 0 && d->sender == NULL) { d->sender = message_address_parse(pool, value, - value_len); + value_len, 0); } break; case 7: @@ -63,8 +69,8 @@ case 8: if (memcasecmp(name, "Reply-To", 8) == 0 && d->reply_to == NULL) { - d->reply_to = - message_address_parse(pool, value, value_len); + d->reply_to = message_address_parse(pool, value, + value_len, 0); } break; case 10: @@ -171,47 +177,55 @@ return FALSE; } + if (*in_group && args[0] == NULL && args[1] == NULL && + args[2] == NULL && args[3] == NULL) { + /* end of group */ + str_append_c(str, ';'); + *in_group = FALSE; + return TRUE; + } + if (str_len(str) > 0) str_append(str, ", "); - if (*in_group) { - if (args[0] == NULL && args[1] == NULL && - args[2] == NULL && args[3] == NULL) { - /* end of group */ - str_append_c(str, ';'); - *in_group = FALSE; - return TRUE; - } - } else { - if (args[0] == NULL && args[1] == NULL && - args[2] != NULL && args[3] == NULL) { - /* beginning of group */ - str_append(str, args[2]); - str_append(str, ": "); - *in_group = TRUE; - return TRUE; - } + if (!*in_group && args[0] == NULL && args[1] == NULL && + args[2] != NULL && args[3] == NULL) { + /* beginning of group */ + str_append(str, args[2]); + str_append(str, ": "); + *in_group = TRUE; + return TRUE; } - /* name <@route:mailbox@domain> */ - if (args[0] != NULL) { - str_append(str, args[0]); - str_append_c(str, ' '); - } + /* a) mailbox@domain + b) name <@route:mailbox@domain> */ + if (args[0] == NULL && args[1] == NULL) { + if (args[2] != NULL) + str_append(str, args[2]); + if (args[3] != NULL) { + str_append_c(str, '@'); + str_append(str, args[3]); + } + } else { + if (args[0] != NULL) { + str_append(str, args[0]); + str_append_c(str, ' '); + } - str_append_c(str, '<'); - if (args[1] != NULL) { - str_append_c(str, '@'); - str_append(str, args[1]); - str_append_c(str, ':'); + str_append_c(str, '<'); + if (args[1] != NULL) { + str_append_c(str, '@'); + str_append(str, args[1]); + str_append_c(str, ':'); + } + if (args[2] != NULL) + str_append(str, args[2]); + if (args[3] != NULL) { + str_append_c(str, '@'); + str_append(str, args[3]); + } + str_append_c(str, '>'); } - if (args[2] != NULL) - str_append(str, args[2]); - if (args[3] != NULL) { - str_append_c(str, '@'); - str_append(str, args[3]); - } - str_append_c(str, '>'); return TRUE; } diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-imap/imap-message-cache.c --- a/src/lib-imap/imap-message-cache.c Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,641 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -#include "lib.h" -#include "istream.h" -#include "mmap-util.h" -#include "message-parser.h" -#include "message-part-serialize.h" -#include "message-size.h" -#include "imap-bodystructure.h" -#include "imap-envelope.h" -#include "imap-message-cache.h" - -#include - -/* It's not very useful to cache lots of messages, as they're mostly wanted - just once. The biggest reason for this cache to exist is to get just the - latest message. */ -#define MAX_CACHED_MESSAGES 16 - -#define DEFAULT_MESSAGE_POOL_SIZE 4096 - -struct cached_message { - struct cached_message *next; - - pool_t pool; - unsigned int uid; - - struct message_part *part; - struct message_size *hdr_size; - struct message_size *body_size; - struct message_size *partial_size; - - time_t internal_date; - uoff_t full_virtual_size; - - char *cached_body; - char *cached_bodystructure; - char *cached_envelope; - - struct message_part_envelope_data *envelope; -}; - -struct imap_message_cache { - struct imap_message_cache_iface *iface; - - struct cached_message *messages; - int messages_count; - - struct cached_message *open_msg; - struct istream *open_stream; - - void *context; -}; - -struct imap_message_cache * -imap_msgcache_alloc(struct imap_message_cache_iface *iface) -{ - struct imap_message_cache *cache; - - cache = i_new(struct imap_message_cache, 1); - cache->iface = iface; - return cache; -} - -static void cached_message_free(struct cached_message *msg) -{ - pool_unref(msg->pool); -} - -void imap_msgcache_clear(struct imap_message_cache *cache) -{ - struct cached_message *next; - - imap_msgcache_close(cache); - - while (cache->messages != NULL) { - next = cache->messages->next; - cached_message_free(cache->messages); - cache->messages = next; - } -} - -void imap_msgcache_free(struct imap_message_cache *cache) -{ - imap_msgcache_clear(cache); - i_free(cache); -} - -static struct cached_message * -cache_new(struct imap_message_cache *cache, unsigned int uid) -{ - struct cached_message *msg, **msgp; - pool_t pool; - - if (cache->messages_count < MAX_CACHED_MESSAGES) - cache->messages_count++; - else { - /* remove the last message from cache */ - msgp = &cache->messages; - while ((*msgp)->next != NULL) - msgp = &(*msgp)->next; - - cached_message_free(*msgp); - *msgp = NULL; - } - - pool = pool_alloconly_create("cached_message", - DEFAULT_MESSAGE_POOL_SIZE); - - msg = p_new(pool, struct cached_message, 1); - msg->pool = pool; - msg->uid = uid; - msg->internal_date = (time_t)-1; - msg->full_virtual_size = (uoff_t)-1; - - msg->next = cache->messages; - cache->messages = msg; - return msg; -} - -static struct cached_message * -cache_open_or_create(struct imap_message_cache *cache, unsigned int uid) -{ - struct cached_message **pos, *msg; - - pos = &cache->messages; - for (; *pos != NULL; pos = &(*pos)->next) { - if ((*pos)->uid == uid) - break; - } - - if (*pos == NULL) { - /* not found, add it */ - msg = cache_new(cache, uid); - } else if (*pos != cache->messages) { - /* move it to first in list */ - msg = *pos; - *pos = msg->next; - - msg->next = cache->messages; - cache->messages = msg; - } else { - msg = *pos; - } - - return msg; -} - -static void parse_envelope_header(struct message_part *part, - const unsigned char *name, size_t name_len, - const unsigned char *value, size_t value_len, - void *context) -{ - struct cached_message *msg = context; - - if (part == NULL || part->parent == NULL) { - /* parse envelope headers if we're at the root message part */ - imap_envelope_parse_header(msg->pool, &msg->envelope, - name, name_len, value, value_len); - } -} - -static int imap_msgcache_get_stream(struct imap_message_cache *cache, - uoff_t offset) -{ - if (cache->open_stream == NULL) - cache->open_stream = cache->iface->open_mail(cache->context); - else if (offset < cache->open_stream->v_offset) { - /* need to rewind */ - cache->open_stream = - cache->iface->stream_rewind(cache->open_stream, - cache->context); - } - - if (cache->open_stream == NULL) - return FALSE; - - i_assert(offset >= cache->open_stream->v_offset); - - i_stream_skip(cache->open_stream, - offset - cache->open_stream->v_offset); - return TRUE; -} - -static void msg_get_part(struct imap_message_cache *cache) -{ - if (cache->open_msg->part == NULL) { - cache->open_msg->part = - cache->iface->get_cached_parts(cache->open_msg->pool, - cache->context); - } -} - -/* Caches the fields for given message if possible */ -static int cache_fields(struct imap_message_cache *cache, - enum imap_cache_field fields) -{ - struct cached_message *msg; - const char *value; - int failed; - - msg = cache->open_msg; - failed = FALSE; - - t_push(); - - if ((fields & IMAP_CACHE_BODYSTRUCTURE) && - msg->cached_bodystructure == NULL) { - value = cache->iface->get_cached_field(IMAP_CACHE_BODYSTRUCTURE, - cache->context); - if (value == NULL && imap_msgcache_get_stream(cache, 0)) { - msg_get_part(cache); - - value = imap_part_get_bodystructure(msg->pool, - &msg->part, - cache->open_stream, - TRUE); - } - - msg->cached_bodystructure = p_strdup(msg->pool, value); - failed = value == NULL; - } - - if ((fields & IMAP_CACHE_BODY) && msg->cached_body == NULL) { - value = cache->iface->get_cached_field(IMAP_CACHE_BODY, - cache->context); - if (value == NULL && cache->open_stream != NULL) { - /* we can generate it from cached BODYSTRUCTURE. - do it only if the file isn't open already, since - this takes more CPU than parsing message headers. */ - value = cache->iface->get_cached_field( - IMAP_CACHE_BODYSTRUCTURE, - cache->context); - if (value != NULL) { - value = imap_body_parse_from_bodystructure( - value); - } - } - - if (value == NULL && imap_msgcache_get_stream(cache, 0)) { - msg_get_part(cache); - - value = imap_part_get_bodystructure(msg->pool, - &msg->part, - cache->open_stream, - FALSE); - } - - msg->cached_body = p_strdup(msg->pool, value); - failed = value == NULL; - } - - if ((fields & IMAP_CACHE_ENVELOPE) && msg->cached_envelope == NULL) { - value = cache->iface->get_cached_field(IMAP_CACHE_ENVELOPE, - cache->context); - if (value == NULL) { - if (msg->envelope == NULL && - imap_msgcache_get_stream(cache, 0)) { - /* envelope isn't parsed yet, do it. header - size is calculated anyway so save it */ - if (msg->hdr_size == NULL) { - msg->hdr_size = - p_new(msg->pool, - struct message_size, 1); - } - - message_parse_header(NULL, cache->open_stream, - msg->hdr_size, - parse_envelope_header, - msg); - } - - value = imap_envelope_get_part_data(msg->envelope); - } - - msg->cached_envelope = p_strdup(msg->pool, value); - failed = value == NULL; - } - - if ((fields & IMAP_CACHE_VIRTUAL_SIZE) && - msg->full_virtual_size == (uoff_t)-1) { - fields |= IMAP_CACHE_MESSAGE_HDR_SIZE | - IMAP_CACHE_MESSAGE_BODY_SIZE; - } - - if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) && msg->body_size == NULL) { - /* we don't have body size. and since we're already going - to scan the whole message body, we might as well build - the message_part. FIXME: this slows down things when it's - not needed, do we really want to? */ - fields |= IMAP_CACHE_MESSAGE_PART; - } - - if (fields & IMAP_CACHE_MESSAGE_PART) { - msg_get_part(cache); - - if (msg->part == NULL && imap_msgcache_get_stream(cache, 0)) { - /* we need to parse the message */ - message_header_callback_t callback; - - if ((fields & IMAP_CACHE_ENVELOPE) && - msg->cached_envelope == NULL) { - /* we need envelope too, fill the info - while parsing headers */ - callback = parse_envelope_header; - } else { - callback = NULL; - } - - msg->part = message_parse(msg->pool, cache->open_stream, - callback, msg); - } - - failed = msg->part == NULL; - } - - if ((fields & IMAP_CACHE_MESSAGE_BODY_SIZE) && - msg->body_size == NULL && msg->part != NULL) { - msg->body_size = p_new(msg->pool, struct message_size, 1); - if (msg->hdr_size == NULL) { - msg->hdr_size = p_new(msg->pool, - struct message_size, 1); - } - - *msg->hdr_size = msg->part->header_size; - *msg->body_size = msg->part->body_size; - } - - if ((fields & IMAP_CACHE_MESSAGE_HDR_SIZE) && msg->hdr_size == NULL) { - msg_get_part(cache); - - msg->hdr_size = p_new(msg->pool, struct message_size, 1); - if (msg->part != NULL) { - /* easy, get it from root part */ - *msg->hdr_size = msg->part->header_size; - - if (msg->body_size == NULL) { - msg->body_size = p_new(msg->pool, - struct message_size, 1); - *msg->body_size = msg->part->body_size; - } - } else { - /* need to do some light parsing */ - if (imap_msgcache_get_stream(cache, 0)) { - message_get_header_size(cache->open_stream, - msg->hdr_size); - } else { - failed = TRUE; - } - } - } - - if ((fields & IMAP_CACHE_VIRTUAL_SIZE) && - msg->full_virtual_size == (uoff_t)-1) { - if (msg->hdr_size == NULL || msg->body_size == NULL) - failed = TRUE; - else { - msg->full_virtual_size = msg->hdr_size->virtual_size + - msg->body_size->virtual_size; - } - } - - if (fields & IMAP_CACHE_MESSAGE_OPEN) { - /* this isn't needed for anything else than pre-opening the - mail and seeing if it fails. */ - failed = !imap_msgcache_get_stream(cache, 0); - } - - if ((fields & IMAP_CACHE_INTERNALDATE) && - msg->internal_date == (time_t)-1) { - /* keep this last, since we may get it when mail file is - opened. */ - msg->internal_date = - cache->iface->get_internal_date(cache->context); - failed = msg->internal_date == (time_t)-1; - } - - t_pop(); - return !failed; -} - -int imap_msgcache_open(struct imap_message_cache *cache, unsigned int uid, - enum imap_cache_field fields, - uoff_t vp_header_size, uoff_t vp_body_size, - uoff_t full_virtual_size, void *context) -{ - struct cached_message *msg; - - msg = cache_open_or_create(cache, uid); - if (cache->open_msg != msg) { - imap_msgcache_close(cache); - cache->open_msg = msg; - } - cache->context = context; - - if (vp_header_size != (uoff_t)-1 && msg->hdr_size == NULL) { - /* physical size == virtual size */ - msg->hdr_size = p_new(msg->pool, struct message_size, 1); - msg->hdr_size->physical_size = msg->hdr_size->virtual_size = - vp_header_size; - } - - if (vp_body_size != (uoff_t)-1 && msg->body_size == NULL) { - /* physical size == virtual size */ - msg->body_size = p_new(msg->pool, struct message_size, 1); - msg->body_size->physical_size = msg->body_size->virtual_size = - vp_body_size; - } - - msg->full_virtual_size = full_virtual_size; - - return cache_fields(cache, fields); -} - -void imap_msgcache_close(struct imap_message_cache *cache) -{ - if (cache->open_stream != NULL) { - i_stream_unref(cache->open_stream); - cache->open_stream = NULL; - } - - cache->open_msg = NULL; - cache->context = NULL; -} - -const char *imap_msgcache_get(struct imap_message_cache *cache, - enum imap_cache_field field) -{ - struct cached_message *msg; - - i_assert(cache->open_msg != NULL); - - msg = cache->open_msg; - switch (field) { - case IMAP_CACHE_BODY: - if (msg->cached_body == NULL) - cache_fields(cache, field); - return msg->cached_body; - case IMAP_CACHE_BODYSTRUCTURE: - if (msg->cached_bodystructure == NULL) - cache_fields(cache, field); - return msg->cached_bodystructure; - case IMAP_CACHE_ENVELOPE: - if (msg->cached_envelope == NULL) - cache_fields(cache, field); - return msg->cached_envelope; - default: - i_unreached(); - } - - return NULL; -} - -struct message_part *imap_msgcache_get_parts(struct imap_message_cache *cache) -{ - if (cache->open_msg->part == NULL) - cache_fields(cache, IMAP_CACHE_MESSAGE_PART); - return cache->open_msg->part; -} - -uoff_t imap_msgcache_get_virtual_size(struct imap_message_cache *cache) -{ - if (cache->open_msg->full_virtual_size == (uoff_t)-1) - cache_fields(cache, IMAP_CACHE_VIRTUAL_SIZE); - return cache->open_msg->full_virtual_size; -} - -time_t imap_msgcache_get_internal_date(struct imap_message_cache *cache) -{ - if (cache->open_msg->internal_date == (time_t)-1) - cache_fields(cache, IMAP_CACHE_INTERNALDATE); - return cache->open_msg->internal_date; -} - -int imap_msgcache_get_rfc822(struct imap_message_cache *cache, - struct istream **stream, - struct message_size *hdr_size, - struct message_size *body_size) -{ - struct cached_message *msg; - uoff_t offset; - - i_assert(cache->open_msg != NULL); - - msg = cache->open_msg; - if (stream != NULL) { - if (msg->hdr_size == NULL) - cache_fields(cache, IMAP_CACHE_MESSAGE_HDR_SIZE); - offset = hdr_size != NULL ? 0 : - msg->hdr_size->physical_size; - if (!imap_msgcache_get_stream(cache, offset)) - return FALSE; - *stream = cache->open_stream; - } - - if (body_size != NULL) { - if (msg->body_size == NULL) - cache_fields(cache, IMAP_CACHE_MESSAGE_BODY_SIZE); - if (msg->body_size == NULL) - return FALSE; - *body_size = *msg->body_size; - } - - if (hdr_size != NULL) { - if (msg->hdr_size == NULL) - cache_fields(cache, IMAP_CACHE_MESSAGE_HDR_SIZE); - if (msg->hdr_size == NULL) - return FALSE; - *hdr_size = *msg->hdr_size; - } - - return TRUE; -} - -static uoff_t get_partial_size(struct istream *stream, - uoff_t virtual_skip, uoff_t max_virtual_size, - struct message_size *partial, - struct message_size *dest, int *cr_skipped) -{ - uoff_t physical_skip; - int last_cr; - - /* see if we can use the existing partial */ - if (partial->virtual_size > virtual_skip) - memset(partial, 0, sizeof(struct message_size)); - else { - i_stream_skip(stream, partial->physical_size); - virtual_skip -= partial->virtual_size; - } - - message_skip_virtual(stream, virtual_skip, partial, cr_skipped); - physical_skip = partial->physical_size; - - if (*cr_skipped && max_virtual_size != (uoff_t)-1) { - /* get_body_size() sees \n first, counting it as \r\n */ - max_virtual_size++; - } - - message_get_body_size(stream, dest, max_virtual_size, &last_cr); - - if (*cr_skipped) { - /* extra virtual \r counted, drop it */ - dest->virtual_size--; - } - - message_size_add(partial, dest); - if (last_cr != 0) { - /* we'll see \n as first character next time, so make sure - we don't count the (virtual) \r twice. */ - i_assert(partial->physical_size > 0); - - if (last_cr == 1) - partial->physical_size--; - partial->virtual_size--; - } - return physical_skip; -} - -int imap_msgcache_get_rfc822_partial(struct imap_message_cache *cache, - uoff_t virtual_skip, - uoff_t max_virtual_size, - int get_header, struct message_size *size, - struct istream **stream, int *cr_skipped) -{ - struct cached_message *msg; - uoff_t physical_skip, full_size; - int size_got; - - i_assert(cache->open_msg != NULL); - - memset(size, 0, sizeof(struct message_size)); - *stream = NULL; - *cr_skipped = FALSE; - - msg = cache->open_msg; - - if (msg->hdr_size == NULL) { - cache_fields(cache, IMAP_CACHE_MESSAGE_HDR_SIZE); - if (msg->hdr_size == NULL) - return FALSE; - } - - /* see if we can do this easily */ - size_got = FALSE; - if (virtual_skip == 0) { - if (msg->body_size == NULL) { - cache_fields(cache, IMAP_CACHE_MESSAGE_BODY_SIZE); - if (msg->body_size == NULL) - return FALSE; - } - - full_size = msg->body_size->virtual_size; - if (get_header) - full_size += msg->hdr_size->virtual_size; - - if (max_virtual_size >= full_size) { - memcpy(size, msg->body_size, sizeof(*size)); - if (get_header) - message_size_add(size, msg->hdr_size); - size_got = TRUE; - } - } - - if (size_got) { - physical_skip = get_header ? 0 : msg->hdr_size->physical_size; - } else { - if (!imap_msgcache_get_stream(cache, 0)) - return FALSE; - - if (msg->partial_size == NULL) { - msg->partial_size = - p_new(msg->pool, struct message_size, 1); - } - if (!get_header) - virtual_skip += msg->hdr_size->virtual_size; - - physical_skip = - get_partial_size(cache->open_stream, virtual_skip, - max_virtual_size, msg->partial_size, - size, cr_skipped); - } - - /* seek to wanted position */ - if (!imap_msgcache_get_stream(cache, physical_skip)) - return FALSE; - - *stream = cache->open_stream; - return TRUE; -} - -int imap_msgcache_get_data(struct imap_message_cache *cache, - struct istream **stream) -{ - i_assert(cache->open_msg != NULL); - - if (!imap_msgcache_get_stream(cache, 0)) - return FALSE; - - *stream = cache->open_stream; - return TRUE; -} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-imap/imap-message-cache.h --- a/src/lib-imap/imap-message-cache.h Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -#ifndef __IMAP_MESSAGE_CACHE_H -#define __IMAP_MESSAGE_CACHE_H - -struct message_part; -struct message_size; - -/* IMAP message cache. Caches are mailbox-specific and must be cleared - if UID validity changes. Also if message data may have changed, - imap_msgcache_close() must be called. - - Caching is mostly done to avoid parsing the same message multiple times - when client fetches the message in parts. -*/ - -enum imap_cache_field { - IMAP_CACHE_BODY = 0x0001, - IMAP_CACHE_BODYSTRUCTURE = 0x0002, - IMAP_CACHE_ENVELOPE = 0x0004, - IMAP_CACHE_INTERNALDATE = 0x0008, - IMAP_CACHE_VIRTUAL_SIZE = 0x0010, - - IMAP_CACHE_MESSAGE_OPEN = 0x0200, - IMAP_CACHE_MESSAGE_PART = 0x0400, - IMAP_CACHE_MESSAGE_HDR_SIZE = 0x0800, - IMAP_CACHE_MESSAGE_BODY_SIZE = 0x0100 -}; - -struct imap_message_cache_iface { - /* Open mail for reading. */ - struct istream *(*open_mail)(void *context); - /* Rewind stream to beginning, possibly closing the old stream - if it can't directly be rewinded. */ - struct istream *(*stream_rewind)(struct istream *stream, void *context); - - /* Returns field if it's already cached, or NULL. */ - const char *(*get_cached_field)(enum imap_cache_field field, - void *context); - /* Returns message_part if it's already cached, or NULL. */ - struct message_part *(*get_cached_parts)(pool_t pool, void *context); - - /* Returns message's internal date, or (time_t)-1 if error. */ - time_t (*get_internal_date)(void *context); -}; - -struct imap_message_cache; - -struct imap_message_cache * -imap_msgcache_alloc(struct imap_message_cache_iface *iface); -void imap_msgcache_clear(struct imap_message_cache *cache); -void imap_msgcache_free(struct imap_message_cache *cache); - -/* Open the specified message. Set vp_*_size if both physical and virtual - sizes are same, otherwise (uoff_t)-1. If full_virtual_size isn't known, - set it to (uoff_t)-1. Returns TRUE if all specified fields were cached. - Even if FALSE is returned, it's possible to use the cached data, - imap_msgcache_get() just returns NULL for those that weren't. */ -int imap_msgcache_open(struct imap_message_cache *cache, unsigned int uid, - enum imap_cache_field fields, - uoff_t vp_header_size, uoff_t vp_body_size, - uoff_t full_virtual_size, void *context); - -/* Close the IOStream for opened message. */ -void imap_msgcache_close(struct imap_message_cache *cache); - -/* Returns the field from cache, or NULL if it's not cached. */ -const char *imap_msgcache_get(struct imap_message_cache *cache, - enum imap_cache_field field); - -/* Returns the root message_part for message, or NULL if failed. */ -struct message_part *imap_msgcache_get_parts(struct imap_message_cache *cache); - -/* Returns the virtual size of message, or (uoff_t)-1 if failed. */ -uoff_t imap_msgcache_get_virtual_size(struct imap_message_cache *cache); - -/* Returns the internal date of message, or (time_t)-1 if failed. */ -time_t imap_msgcache_get_internal_date(struct imap_message_cache *cache); - -/* Returns TRUE if successful. If stream is not NULL, it's set to point to - beginning of message, or to beginning of message body if hdr_size is NULL. */ -int imap_msgcache_get_rfc822(struct imap_message_cache *cache, - struct istream **stream, - struct message_size *hdr_size, - struct message_size *body_size); - -/* Returns TRUE if successful. *stream is set to point to the first non-skipped - character. size is set to specify the actual message size in - virtual_skip..max_virtual_size range. cr_skipped is set to TRUE if first - character in stream is LF, and we should NOT treat it as CR+LF. */ -int imap_msgcache_get_rfc822_partial(struct imap_message_cache *cache, - uoff_t virtual_skip, - uoff_t max_virtual_size, - int get_header, struct message_size *size, - struct istream **stream, int *cr_skipped); - -/* Returns TRUE if successful. *stream is set to point to beginning of - message. */ -int imap_msgcache_get_data(struct imap_message_cache *cache, - struct istream **stream); - -#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-index/mail-index-data.c --- a/src/lib-index/mail-index-data.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-index/mail-index-data.c Mon Jan 20 16:52:51 2003 +0200 @@ -139,12 +139,12 @@ } if (size != 0) { + if (pos + size <= data->mmap_used_length) + return TRUE; + debug_mprotect(data->mmap_base, data->mmap_full_length, data->index); - if (pos + size <= data->mmap_used_length) - return TRUE; - if (pos + size <= data->mmap_full_length) { data->mmap_used_length = data->header->used_file_size; if (data->mmap_used_length >= diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-address.c --- a/src/lib-mail/message-address.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-address.c Mon Jan 20 16:52:51 2003 +0200 @@ -19,7 +19,8 @@ } struct message_address * -message_address_parse(pool_t pool, const unsigned char *data, size_t size) +message_address_parse(pool_t pool, const unsigned char *data, size_t size, + unsigned int max_addresses) { static const enum message_token stop_tokens_init[] = { ',', '@', '<', ':', TOKEN_LAST }; @@ -74,8 +75,11 @@ ingroup = FALSE; len = 0; stop_tokens = stop_tokens_init; + if (max_addresses == 0) + max_addresses = (unsigned int)-1; + next_phrase = mailbox; stop = FALSE; - while (!stop) { + while (!stop && max_addresses > 0) { if (next_phrase == name && str_len(name) > 0) { /* continuing previously started name, separate it from us with space */ @@ -101,6 +105,7 @@ if (str_len(mailbox) > 0 || str_len(domain) > 0 || str_len(route) > 0 || str_len(name) > 0) { addr = new_address(pool, &next_addr); + max_addresses--; addr->mailbox = p_strdup(pool, str_c(mailbox)); addr->domain = str_len(domain) == 0 ? NULL : p_strdup(pool, str_c(domain)); @@ -115,6 +120,7 @@ /* end of group - add end of group marker */ ingroup = FALSE; (void)new_address(pool, &next_addr); + max_addresses--; } if (token == TOKEN_LAST) { @@ -187,6 +193,7 @@ case ':': /* beginning of group */ addr = new_address(pool, &next_addr); + max_addresses--; addr->name = p_strdup(pool, str_c(mailbox)); str_truncate(mailbox, 0); @@ -210,3 +217,61 @@ return first_addr; } +void message_address_write(string_t *str, const struct message_address *addr) +{ + int first = TRUE, in_group = FALSE; + + /* a) mailbox@domain + b) name <@route:mailbox@domain> + c) group: .. ; */ + + while (addr != NULL) { + if (first) + first = FALSE; + else + str_append(str, ", "); + + if (addr->mailbox == NULL && addr->domain == NULL) { + if (!in_group) { + if (addr->name != NULL) + str_append(str, addr->name); + str_append(str, ": "); + first = TRUE; + } else { + i_assert(addr->name == NULL); + + /* cut out the ", " */ + str_truncate(str, str_len(str)-2); + str_append_c(str, ';'); + } + + in_group = !in_group; + } else if ((addr->name == NULL || *addr->name == '\0') && + addr->route == NULL) { + i_assert(addr->mailbox != NULL); + i_assert(addr->domain != NULL); + + str_append(str, addr->mailbox); + str_append_c(str, '@'); + str_append(str, addr->domain); + } else { + i_assert(addr->mailbox != NULL); + i_assert(addr->domain != NULL); + + if (addr->name != NULL) { + str_append(str, addr->name); + str_append_c(str, ' '); + } + str_append_c(str, '<'); + if (addr->route != NULL) { + str_append_c(str, '@'); + str_append(str, addr->route); + str_append_c(str, ':'); + } + str_append(str, addr->mailbox); + str_append_c(str, '@'); + str_append(str, addr->domain); + str_append_c(str, '>'); + } + } +} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-address.h --- a/src/lib-mail/message-address.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-address.h Mon Jan 20 16:52:51 2003 +0200 @@ -1,13 +1,22 @@ #ifndef __MESSAGE_ADDRESS_H #define __MESSAGE_ADDRESS_H +/* group: ... ; will be stored like: + {name = "group", NULL, NULL, NULL}, ..., {NULL, NULL, NULL, NULL} +*/ struct message_address { struct message_address *next; const char *name, *route, *mailbox, *domain; }; +/* data and size are passed directly to message_tokenize_init(), so (size_t)-1 + can be given if data is \0 terminated. If there's more than max_addresses, + the rest are skipped. Setting max_addresses to 0 disables this. */ struct message_address * -message_address_parse(pool_t pool, const unsigned char *data, size_t size); +message_address_parse(pool_t pool, const unsigned char *data, size_t size, + unsigned int max_addresses); + +void message_address_write(string_t *str, const struct message_address *addr); #endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-body-search.c --- a/src/lib-mail/message-body-search.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-body-search.c Mon Jan 20 16:52:51 2003 +0200 @@ -255,7 +255,7 @@ static int message_search_body(struct part_search_context *ctx, struct istream *input, - struct message_part *part) + const struct message_part *part) { const unsigned char *data; buffer_t *decodebuf; @@ -368,7 +368,7 @@ static int message_body_search_ctx(struct body_search_context *ctx, struct istream *input, - struct message_part *part) + const struct message_part *part) { struct part_search_context part_ctx; int found; @@ -410,7 +410,7 @@ int message_body_search(const char *key, const char *charset, int *unknown_charset, struct istream *input, - struct message_part *part, int search_header) + const struct message_part *part, int search_header) { struct body_search_context ctx; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-body-search.h --- a/src/lib-mail/message-body-search.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-body-search.h Mon Jan 20 16:52:51 2003 +0200 @@ -9,6 +9,6 @@ specific charset but is compared to message data without any translation. */ int message_body_search(const char *key, const char *charset, int *unknown_charset, struct istream *input, - struct message_part *part, int search_header); + const struct message_part *part, int search_header); #endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-date.c --- a/src/lib-mail/message-date.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-date.c Mon Jan 20 16:52:51 2003 +0200 @@ -213,7 +213,8 @@ return TRUE; } -int message_date_parse(const char *data, time_t *time, int *timezone_offset) +int message_date_parse(const unsigned char *data, size_t size, + time_t *time, int *timezone_offset) { struct message_tokenizer *ctx; int ret; @@ -221,8 +222,7 @@ if (data == NULL || *data == '\0') return FALSE; - ctx = message_tokenize_init((const unsigned char *) data, (size_t)-1, - NULL, NULL); + ctx = message_tokenize_init(data, size, NULL, NULL); ret = mail_date_parse_tokens(ctx, time, timezone_offset); message_tokenize_deinit(ctx); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-date.h --- a/src/lib-mail/message-date.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-date.h Mon Jan 20 16:52:51 2003 +0200 @@ -3,7 +3,8 @@ /* Parses RFC2822 date/time string. timezone_offset is filled with the timezone's difference to UTC in minutes. */ -int message_date_parse(const char *data, time_t *time, int *timezone_offset); +int message_date_parse(const unsigned char *data, size_t size, + time_t *time, int *timezone_offset); /* Create RFC2822 date/time string from given time in local timezone. */ const char *message_date_create(time_t time); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-parser.h --- a/src/lib-mail/message-parser.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-parser.h Mon Jan 20 16:52:51 2003 +0200 @@ -1,6 +1,8 @@ #ifndef __MESSAGE_PARSER_H #define __MESSAGE_PARSER_H +#include "message-size.h" + #define IS_LWSP(c) \ ((c) == ' ' || (c) == '\t') @@ -16,12 +18,6 @@ MESSAGE_PART_FLAG_BINARY = 0x10 }; -struct message_size { - uoff_t physical_size; - uoff_t virtual_size; - unsigned int lines; -}; - struct message_part { struct message_part *parent; struct message_part *next; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-send.c --- a/src/lib-mail/message-send.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-send.c Mon Jan 20 16:52:51 2003 +0200 @@ -8,7 +8,7 @@ #include "message-size.h" int message_send(struct ostream *output, struct istream *input, - struct message_size *msg_size, + const struct message_size *msg_size, uoff_t virtual_skip, uoff_t max_virtual_size) { const unsigned char *msg; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-send.h --- a/src/lib-mail/message-send.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-send.h Mon Jan 20 16:52:51 2003 +0200 @@ -8,7 +8,7 @@ use (uoff_t)-1. Remember that if input begins with LF, CR is inserted before it unless virtual_skip = 1. Returns TRUE if successful. */ int message_send(struct ostream *output, struct istream *input, - struct message_size *msg_size, + const struct message_size *msg_size, uoff_t virtual_skip, uoff_t max_virtual_size); #endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-size.h --- a/src/lib-mail/message-size.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-size.h Mon Jan 20 16:52:51 2003 +0200 @@ -1,7 +1,11 @@ #ifndef __MESSAGE_SIZE_H #define __MESSAGE_SIZE_H -struct message_size; +struct message_size { + uoff_t physical_size; + uoff_t virtual_size; + unsigned int lines; +}; /* Calculate size of message header. Leave the input point to first character in body. */ diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-mail/message-tokenize.h --- a/src/lib-mail/message-tokenize.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-mail/message-tokenize.h Mon Jan 20 16:52:51 2003 +0200 @@ -34,9 +34,9 @@ char missing_char, void *context); -/* Tokenize the string. Returns NULL if string is empty. Memory for - returned array is allocated from data stack. You don't have to use - the tokens_count, since last token is always 0. */ +/* Initialize message tokenizer. data is parsed until \0 is found, or size + bytes has been parsed, so it's possible to give (size_t)-1 as size + if the string is \0 terminated. */ struct message_tokenizer * message_tokenize_init(const unsigned char *data, size_t size, message_tokenize_error_callback_t error_cb, diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/Makefile.am --- a/src/lib-storage/Makefile.am Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/Makefile.am Mon Jan 20 16:52:51 2003 +0200 @@ -9,12 +9,8 @@ libstorage_a_SOURCES = \ mail-search.c \ - mail-sort.c \ - mail-storage.c \ - mail-thread.c + mail-storage.c noinst_HEADERS = \ mail-search.h \ - mail-sort.h \ - mail-storage.h \ - mail-thread.h + mail-storage.h diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/Makefile.am --- a/src/lib-storage/index/Makefile.am Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/Makefile.am Mon Jan 20 16:52:51 2003 +0200 @@ -13,20 +13,16 @@ index-copy.c \ index-expunge.c \ index-fetch.c \ - index-fetch-section.c \ + index-mail.c \ index-mailbox-check.c \ index-messageset.c \ - index-msgcache.c \ index-save.c \ index-search.c \ - index-sort.c \ index-status.c \ index-storage.c \ index-sync.c \ index-update-flags.c noinst_HEADERS = \ - index-fetch.h \ index-messageset.h \ - index-sort.h \ index-storage.h diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-copy.c --- a/src/lib-storage/index/index-copy.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-copy.c Mon Jan 20 16:52:51 2003 +0200 @@ -8,51 +8,49 @@ #include -struct copy_context { - struct mailbox *dest; - const char **custom_flags; - int copy_inside_mailbox; -}; - -static int copy_cb(struct mail_index *index, struct mail_index_record *rec, - unsigned int client_seq __attr_unused__, - unsigned int idx_seq __attr_unused__, void *context) +static int copy_messageset(struct messageset_context *ctx, + struct index_mailbox *src, struct mailbox *dest) { - struct copy_context *ctx = context; - struct index_mailbox *dest_ibox = NULL; + const struct messageset_mail *mail; + struct mail_full_flags flags; struct istream *input; time_t internal_date; int failed, deleted; - input = index->open_mail(index, rec, &internal_date, &deleted); - if (input == NULL) - return FALSE; + memset(&flags, 0, sizeof(flags)); + flags.custom_flags = + mail_custom_flags_list_get(src->index->custom_flags); + flags.custom_flags_count = MAIL_CUSTOM_FLAGS_COUNT; - if (ctx->copy_inside_mailbox) { - /* kludgy.. */ - dest_ibox = (struct index_mailbox *) ctx->dest; - dest_ibox->delay_save_unlocking = TRUE; + while ((mail = index_messageset_next(ctx)) != NULL) { + input = src->index->open_mail(src->index, mail->rec, + &internal_date, &deleted); + if (input == NULL) { + if (deleted) + continue; + return FALSE; + } + + /* save it in destination mailbox */ + flags.flags = mail->rec->msg_flags; + failed = !dest->save(dest, &flags, internal_date, 0, + input, input->v_limit); + i_stream_unref(input); + + if (failed) + return FALSE; } - /* save it in destination mailbox */ - failed = !ctx->dest->save(ctx->dest, rec->msg_flags, - ctx->custom_flags, internal_date, 0, - input, input->v_limit); - - if (ctx->copy_inside_mailbox) - dest_ibox->delay_save_unlocking = FALSE; - - i_stream_unref(input); - return !failed; + return TRUE; } int index_storage_copy(struct mailbox *box, struct mailbox *destbox, const char *messageset, int uidset) { struct index_mailbox *ibox = (struct index_mailbox *) box; - struct copy_context ctx; + struct messageset_context *ctx; enum mail_lock_type lock_type; - int failed; + int ret, copy_inside_mailbox; if (destbox->readonly) { mail_storage_set_error(box->storage, @@ -60,15 +58,18 @@ return FALSE; } - ctx.copy_inside_mailbox = + copy_inside_mailbox = destbox->storage == box->storage && strcmp(destbox->name, box->name) == 0; - if (ctx.copy_inside_mailbox) { + if (copy_inside_mailbox) { /* copying inside same mailbox */ if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE)) return FALSE; + /* kludgy.. */ + ((struct index_mailbox *) destbox)->delay_save_unlocking = TRUE; + lock_type = MAIL_LOCK_EXCLUSIVE; } else { lock_type = MAIL_LOCK_SHARED; @@ -77,15 +78,16 @@ if (!index_storage_sync_and_lock(ibox, TRUE, lock_type)) return FALSE; - ctx.custom_flags = - mail_custom_flags_list_get(ibox->index->custom_flags); - ctx.dest = destbox; + ctx = index_messageset_init(ibox, messageset, uidset); + ret = copy_messageset(ctx, ibox, destbox); + if (index_messageset_deinit(ctx) < 0) + ret = FALSE; - failed = index_messageset_foreach(ibox, messageset, uidset, - copy_cb, &ctx) <= 0; + if (copy_inside_mailbox) + ((struct index_mailbox *) destbox)->delay_save_unlocking = TRUE; if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK)) return FALSE; - return !failed; + return ret; } diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-fetch-section.c --- a/src/lib-storage/index/index-fetch-section.c Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,491 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -#include "lib.h" -#include "str.h" -#include "istream.h" -#include "ostream.h" -#include "message-send.h" -#include "index-storage.h" -#include "index-fetch.h" - -#include -#include - -struct fetch_header_field_context { - string_t *dest; - struct ostream *output; - uoff_t dest_size; - - uoff_t skip, max_size; - const char *const *fields; - int (*match_func) (const char *const *, const unsigned char *, size_t); -}; - -/* For FETCH[HEADER.FIELDS*] we need to modify the header data before sending - it. We can either save it in memory and then send it, or we can parse it - twice, first calculating the size and then send it. This value specifies - the maximum amount of memory we allow to allocate before using - double-parsing. */ -#define MAX_HEADER_BUFFER_SIZE (32*1024) - -#define UNSIGNED_CRLF (const unsigned char *) "\r\n" - -enum imap_cache_field index_fetch_body_get_cache(const char *section) -{ - if (*section >= '0' && *section <= '9') - return IMAP_CACHE_MESSAGE_PART | IMAP_CACHE_MESSAGE_OPEN; - - if (*section == '\0' || strcasecmp(section, "TEXT") == 0) { - /* no IMAP_CACHE_MESSAGE_BODY_SIZE, so that we don't - uselessly check it when we want to read partial data */ - return IMAP_CACHE_MESSAGE_OPEN; - } - - if (strncasecmp(section, "HEADER", 6) == 0 || - strcasecmp(section, "MIME") == 0) - return IMAP_CACHE_MESSAGE_HDR_SIZE | IMAP_CACHE_MESSAGE_OPEN; - - /* error */ - return 0; -} - -/* fetch BODY[] or BODY[TEXT] */ -static int fetch_body(struct mail_index_record *rec, - struct mail_fetch_body_data *sect, - struct fetch_context *ctx, - const char *prefix, int fetch_header) -{ - struct message_size size; - struct istream *input; - const char *str; - int cr_skipped; - - if (!imap_msgcache_get_rfc822_partial(ctx->cache, sect->skip, - sect->max_size, fetch_header, - &size, &input, &cr_skipped)) { - i_error("Couldn't get BODY[] for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } - - str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", - prefix, size.virtual_size); - if (o_stream_send_str(ctx->output, str) < 0) - return FALSE; - - if (cr_skipped) - size.virtual_size++; - - return message_send(ctx->output, input, &size, - cr_skipped ? 1 : 0, sect->max_size); -} - -static const char **get_fields_array(const char *fields) -{ - const char **field_list, **field; - - while (*fields == ' ') - fields++; - if (*fields == '(') - fields++; - - field_list = t_strsplit(fields, " )"); - - /* array ends at ")" element */ - for (field = field_list; *field != NULL; field++) { - if (strcasecmp(*field, ")") == 0) - *field = NULL; - } - - return field_list; -} - -static int header_match(const char *const *fields, - const unsigned char *name, size_t size) -{ - const unsigned char *name_start, *name_end; - const char *field; - - if (size == 0) - return FALSE; - - name_start = name; - name_end = name + size; - - for (; *fields != NULL; fields++) { - field = *fields; - if (*field == '\0') - continue; - - for (name = name_start; name != name_end; name++) { - /* field has been uppercased long time ago while - parsing FETCH command */ - if (i_toupper(*name) != *field) - break; - - field++; - if (*field == '\0') { - if (name+1 == name_end) - return TRUE; - break; - } - } - } - - return FALSE; -} - -static int header_match_not(const char *const *fields, - const unsigned char *name, size_t size) -{ - return !header_match(fields, name, size); -} - -static int header_match_mime(const char *const *fields __attr_unused__, - const unsigned char *name, size_t size) -{ - if (size > 8 && memcasecmp(name, "Content-", 8) == 0) - return TRUE; - - if (size == 12 && memcasecmp(name, "Mime-Version", 12) == 0) - return TRUE; - - return FALSE; -} - -static int fetch_header_append(struct fetch_header_field_context *ctx, - const unsigned char *str, size_t size) -{ - if (ctx->skip > 0) { - if (ctx->skip >= size) { - ctx->skip -= size; - return TRUE; - } - - str += ctx->skip; - size -= ctx->skip; - ctx->skip = 0; - } - - if (ctx->dest_size + size > ctx->max_size) { - i_assert(ctx->dest_size <= ctx->max_size); - size = ctx->max_size - ctx->dest_size; - } - - if (ctx->dest != NULL) - str_append_n(ctx->dest, str, size); - ctx->dest_size += size; - - if (ctx->output != NULL) { - if (o_stream_send(ctx->output, str, size) < 0) - return FALSE; - } - return ctx->dest_size < ctx->max_size; -} - -static void fetch_header_field(struct message_part *part __attr_unused__, - const unsigned char *name, size_t name_len, - const unsigned char *value __attr_unused__, - size_t value_len __attr_unused__, - void *context) -{ - struct fetch_header_field_context *ctx = context; - const unsigned char *field_start, *field_end, *cr, *p; - - /* see if we want this field. */ - if (!ctx->match_func(ctx->fields, name, name_len)) - return; - - /* add the field, inserting CRs when needed. FIXME: is this too - kludgy? we assume name continues with ": value". but otherwise - we wouldn't reply with correct LWSP between ":". */ - field_start = name; - field_end = value + value_len; - - cr = NULL; - for (p = field_start; p != field_end; p++) { - if (*p == '\r') - cr = p; - else if (*p == '\n' && cr != p-1) { - /* missing CR */ - if (!fetch_header_append(ctx, field_start, - (size_t) (p-field_start))) - return; - if (!fetch_header_append(ctx, UNSIGNED_CRLF, 2)) - return; - - field_start = p+1; - } - } - - if (field_start != field_end) { - if (!fetch_header_append(ctx, field_start, - (size_t) (field_end-field_start))) - return; - } - - (void)fetch_header_append(ctx, UNSIGNED_CRLF, 2); -} - -static int fetch_header_fields(struct istream *input, const char *section, - struct fetch_header_field_context *ctx) -{ - if (strncasecmp(section, "HEADER.FIELDS ", 14) == 0) { - ctx->fields = get_fields_array(section + 14); - ctx->match_func = header_match; - } else if (strncasecmp(section, "HEADER.FIELDS.NOT ", 18) == 0) { - ctx->fields = get_fields_array(section + 18); - ctx->match_func = header_match_not; - } else if (strcasecmp(section, "MIME") == 0) { - /* Mime-Version + Content-* fields */ - ctx->match_func = header_match_mime; - } else { - /* invalid section given by user - FIXME: tell user about it */ - return FALSE; - } - - ctx->dest_size = 0; - message_parse_header(NULL, input, NULL, fetch_header_field, ctx); - - /* FIXME: The blank line must not be filtered, says RFC. However, we - shouldn't add it if it wasn't there in the first place. Not very - easy to know currently so we'll just do it always, it'll be present - in all sane messages anyway.. */ - (void)fetch_header_append(ctx, UNSIGNED_CRLF, 2); - - i_assert(ctx->dest_size <= ctx->max_size); - i_assert(ctx->dest == NULL || str_len(ctx->dest) == ctx->dest_size); - return TRUE; -} - -/* fetch wanted headers from given data */ -static int fetch_header_from(struct istream *input, struct ostream *output, - const char *prefix, struct message_size *size, - const char *section, - struct mail_fetch_body_data *sect) -{ - struct fetch_header_field_context ctx; - const char *str; - uoff_t start_offset; - int failed; - - /* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */ - - if (strcasecmp(section, "HEADER") == 0) { - /* all headers */ - str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", - prefix, size->virtual_size); - if (o_stream_send_str(output, str) < 0) - return FALSE; - return message_send(output, input, size, - sect->skip, sect->max_size); - } - - /* partial headers - copy the wanted fields into memory, inserting - missing CRs on the way. If the header is too large, calculate - the size first and then send the data directly to output stream. */ - - memset(&ctx, 0, sizeof(ctx)); - ctx.skip = sect->skip; - ctx.max_size = sect->max_size; - - failed = FALSE; - start_offset = input->v_offset; - - t_push(); - - /* first pass, we need at least the size */ - if (size->virtual_size > MAX_HEADER_BUFFER_SIZE && - sect->max_size > MAX_HEADER_BUFFER_SIZE) { - if (!fetch_header_fields(input, section, &ctx)) - failed = TRUE; - - i_assert(ctx.dest_size <= size->virtual_size); - } else { - ctx.dest = t_str_new(size->virtual_size < 4096 ? - size->virtual_size : 4096); - if (!fetch_header_fields(input, section, &ctx)) - failed = TRUE; - } - - if (!failed) { - str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", - prefix, ctx.dest_size); - if (o_stream_send_str(output, str) < 0) - failed = TRUE; - } - - if (!failed) { - if (ctx.dest == NULL) { - /* second pass, write the data to output stream */ - uoff_t first_size = ctx.dest_size; - - ctx.output = output; - i_stream_seek(input, start_offset); - - if (!failed && - !fetch_header_fields(input, section, &ctx)) - failed = TRUE; - - i_assert(first_size == ctx.dest_size); - } else { - if (o_stream_send(output, str_c(ctx.dest), - str_len(ctx.dest)) < 0) - failed = TRUE; - } - } - - t_pop(); - return !failed; -} - -/* fetch BODY[HEADER...] */ -static int fetch_header(struct mail_fetch_body_data *sect, - struct fetch_context *ctx, const char *prefix) -{ - struct message_size hdr_size; - struct istream *input; - - if (!imap_msgcache_get_rfc822(ctx->cache, &input, &hdr_size, NULL)) - return FALSE; - - return fetch_header_from(input, ctx->output, prefix, &hdr_size, - sect->section, sect); -} - -/* Find message_part for section (eg. 1.3.4) */ -static struct message_part * -part_find(struct mail_fetch_body_data *sect, struct fetch_context *ctx, - const char **section) -{ - struct message_part *part; - const char *path; - unsigned int num; - - part = imap_msgcache_get_parts(ctx->cache); - - path = sect->section; - while (*path >= '0' && *path <= '9' && part != NULL) { - /* get part number */ - num = 0; - while (*path != '\0' && *path != '.') { - if (*path < '0' || *path > '9') - return NULL; - num = num*10 + (*path - '0'); - path++; - } - - if (*path == '.') - path++; - - if (part->flags & MESSAGE_PART_FLAG_MULTIPART) { - /* find the part */ - part = part->children; - for (; num > 1 && part != NULL; num--) - part = part->next; - } else { - /* only 1 allowed with non-multipart messages */ - if (num != 1) - return NULL; - } - - if (part != NULL && - (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822)) { - /* skip the message/rfc822 part */ - part = part->children; - } - } - - *section = path; - return part; -} - -/* fetch BODY[1.2] or BODY[1.2.TEXT] */ -static int fetch_part_body(struct message_part *part, - struct mail_fetch_body_data *sect, - struct fetch_context *ctx, const char *prefix) -{ - struct istream *input; - const char *str; - uoff_t skip_pos; - - if (!imap_msgcache_get_data(ctx->cache, &input)) - return FALSE; - - /* jump to beginning of wanted data */ - skip_pos = part->physical_pos + part->header_size.physical_size; - i_stream_skip(input, skip_pos); - - str = t_strdup_printf("%s {%"PRIuUOFF_T"}\r\n", - prefix, part->body_size.virtual_size); - if (o_stream_send_str(ctx->output, str) < 0) - return FALSE; - - /* FIXME: potential performance problem with big messages: - FETCH BODY[1]<100000..1024>, hopefully no clients do this */ - return message_send(ctx->output, input, &part->body_size, - sect->skip, sect->max_size); -} - -/* fetch BODY[1.2.MIME|HEADER...] */ -static int fetch_part_header(struct message_part *part, const char *section, - struct mail_fetch_body_data *sect, - struct fetch_context *ctx, const char *prefix) -{ - struct istream *input; - - if (!imap_msgcache_get_data(ctx->cache, &input)) - return FALSE; - - i_stream_skip(input, part->physical_pos); - return fetch_header_from(input, ctx->output, prefix, &part->header_size, - section, sect); -} - -static int fetch_part(struct mail_fetch_body_data *sect, - struct fetch_context *ctx, const char *prefix) -{ - struct message_part *part; - const char *section; - - part = part_find(sect, ctx, §ion); - if (part == NULL) - return FALSE; - - if (*section == '\0' || strcasecmp(section, "TEXT") == 0) - return fetch_part_body(part, sect, ctx, prefix); - - if (strncasecmp(section, "HEADER", 6) == 0) - return fetch_part_header(part, section, sect, ctx, prefix); - if (strcasecmp(section, "MIME") == 0) - return fetch_part_header(part, section, sect, ctx, prefix); - - return FALSE; -} - -int index_fetch_body_section(struct mail_index_record *rec, - struct mail_fetch_body_data *sect, - struct fetch_context *ctx) -{ - const char *prefix; - - prefix = !sect->skip_set ? - t_strdup_printf(" BODY[%s]", sect->section) : - t_strdup_printf(" BODY[%s]<%"PRIuUOFF_T">", - sect->section, sect->skip); - if (ctx->first) { - prefix++; ctx->first = FALSE; - } - - if (*sect->section == '\0') - return fetch_body(rec, sect, ctx, prefix, TRUE); - if (strcasecmp(sect->section, "TEXT") == 0) - return fetch_body(rec, sect, ctx, prefix, FALSE); - if (strncasecmp(sect->section, "HEADER", 6) == 0) - return fetch_header(sect, ctx, prefix); - if (*sect->section >= '0' && *sect->section <= '9') - return fetch_part(sect, ctx, prefix); - - /* FIXME: point the error to user */ - return FALSE; -} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-fetch.c --- a/src/lib-storage/index/index-fetch.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-fetch.c Mon Jan 20 16:52:51 2003 +0200 @@ -3,414 +3,157 @@ #include "lib.h" #include "ostream.h" #include "str.h" +#include "mail-index.h" +#include "mail-modifylog.h" #include "mail-custom-flags.h" #include "index-storage.h" -#include "index-fetch.h" #include "index-messageset.h" -#include "message-send.h" -#include "imap-date.h" -#include "imap-util.h" -#include "imap-message-cache.h" - -#include - -static int index_fetch_internaldate(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - time_t date; - - date = imap_msgcache_get_internal_date(ctx->cache); - if (date != (time_t)-1) { - str_printfa(ctx->str, "INTERNALDATE \"%s\" ", - imap_to_datetime(date)); - return TRUE; - } else { - mail_storage_set_critical(ctx->storage, - "Couldn't generate INTERNALDATE for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } -} - -static int index_fetch_body(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - const char *body; - - body = imap_msgcache_get(ctx->cache, IMAP_CACHE_BODY); - if (body != NULL) { - str_printfa(ctx->str, "BODY (%s) ", body); - return TRUE; - } else { - mail_storage_set_critical(ctx->storage, - "Couldn't generate BODY for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } -} - -static int index_fetch_bodystructure(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - const char *bodystructure; - - bodystructure = imap_msgcache_get(ctx->cache, IMAP_CACHE_BODYSTRUCTURE); - if (bodystructure != NULL) { - str_printfa(ctx->str, "BODYSTRUCTURE (%s) ", bodystructure); - return TRUE; - } else { - mail_storage_set_critical(ctx->storage, - "Couldn't generate BODYSTRUCTURE for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } -} - -static int index_fetch_envelope(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - const char *envelope; - - envelope = imap_msgcache_get(ctx->cache, IMAP_CACHE_ENVELOPE); - if (envelope != NULL) { - str_printfa(ctx->str, "ENVELOPE (%s) ", envelope); - return TRUE; - } else { - mail_storage_set_critical(ctx->storage, - "Couldn't generate ENVELOPE for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } -} - -static int index_fetch_rfc822_size(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - uoff_t size; +#include "index-mail.h" - size = imap_msgcache_get_virtual_size(ctx->cache); - if (size == (uoff_t)-1) { - mail_storage_set_critical(ctx->storage, - "Couldn't get RFC822.SIZE for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } - - str_printfa(ctx->str, "RFC822.SIZE %"PRIuUOFF_T" ", size); - return TRUE; -} - -static void index_fetch_flags(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - enum mail_flags flags; - - flags = rec->msg_flags; - if (rec->uid >= ctx->index->first_recent_uid) - flags |= MAIL_RECENT; - if (ctx->update_seen) - flags |= MAIL_SEEN; - - str_printfa(ctx->str, "FLAGS (%s) ", - imap_write_flags(flags, ctx->custom_flags, - ctx->custom_flags_count)); -} - -static void index_fetch_uid(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - str_printfa(ctx->str, "UID %u ", rec->uid); -} - -static int index_fetch_send_rfc822(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - struct message_size hdr_size, body_size; - struct istream *input; - const char *str; - - if (!imap_msgcache_get_rfc822(ctx->cache, &input, - &hdr_size, &body_size)) { - mail_storage_set_critical(ctx->storage, - "Couldn't get RFC822 for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } - - str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n", - hdr_size.virtual_size + body_size.virtual_size); - if (ctx->first) { - str++; ctx->first = FALSE; - } - if (o_stream_send_str(ctx->output, str) < 0) - return FALSE; - - body_size.physical_size += hdr_size.physical_size; - body_size.virtual_size += hdr_size.virtual_size; - return message_send(ctx->output, input, &body_size, 0, (uoff_t)-1); -} - -static int index_fetch_send_rfc822_header(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - struct message_size hdr_size; - struct istream *input; - const char *str; - - if (!imap_msgcache_get_rfc822(ctx->cache, &input, &hdr_size, NULL)) { - mail_storage_set_critical(ctx->storage, - "Couldn't get RFC822.HEADER for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } - - str = t_strdup_printf(" RFC822.HEADER {%"PRIuUOFF_T"}\r\n", - hdr_size.virtual_size); - if (ctx->first) { - str++; ctx->first = FALSE; - } - if (o_stream_send_str(ctx->output, str) < 0) - return FALSE; - - return message_send(ctx->output, input, &hdr_size, 0, (uoff_t)-1); -} +struct mail_fetch_context { + struct index_mailbox *ibox; + struct mail_index *index; -static int index_fetch_send_rfc822_text(struct mail_index_record *rec, - struct fetch_context *ctx) -{ - struct message_size body_size; - struct istream *input; - const char *str; - - if (!imap_msgcache_get_rfc822(ctx->cache, &input, NULL, &body_size)) { - mail_storage_set_critical(ctx->storage, - "Couldn't get RFC822.TEXT for UID %u (index %s)", - rec->uid, ctx->index->filepath); - return FALSE; - } - - str = t_strdup_printf(" RFC822.TEXT {%"PRIuUOFF_T"}\r\n", - body_size.virtual_size); - if (ctx->first) { - str++; ctx->first = FALSE; - } - if (o_stream_send_str(ctx->output, str) < 0) - return FALSE; - - return message_send(ctx->output, input, &body_size, 0, (uoff_t)-1); -} - -static enum imap_cache_field index_get_cache(struct mail_fetch_data *fetch_data) -{ - struct mail_fetch_body_data *sect; - enum imap_cache_field field; - - field = 0; - if (fetch_data->body) - field |= IMAP_CACHE_BODY; - if (fetch_data->bodystructure) - field |= IMAP_CACHE_BODYSTRUCTURE; - if (fetch_data->envelope) - field |= IMAP_CACHE_ENVELOPE; - if (fetch_data->internaldate) - field |= IMAP_CACHE_INTERNALDATE; - - if (fetch_data->rfc822_size) - field |= IMAP_CACHE_VIRTUAL_SIZE; - if (fetch_data->rfc822) { - field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE | - IMAP_CACHE_MESSAGE_BODY_SIZE; - } - if (fetch_data->rfc822_header) - field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE; - if (fetch_data->rfc822_text) - field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_BODY_SIZE; - - /* check what body[] sections want */ - sect = fetch_data->body_sections; - for (; sect != NULL; sect = sect->next) - field |= index_fetch_body_get_cache(sect->section); - return field; -} - -static int fetch_msgcache_open(struct fetch_context *ctx, - struct mail_index_record *rec) -{ - enum imap_cache_field fields; - - fields = index_get_cache(ctx->fetch_data); - if (fields == 0) - return TRUE; - - return index_msgcache_open(ctx->cache, ctx->index, rec, fields); -} - -static int index_fetch_mail(struct mail_index *index __attr_unused__, - struct mail_index_record *rec, - unsigned int client_seq, unsigned int idx_seq, - void *context) -{ - struct fetch_context *ctx = context; - struct mail_fetch_body_data *sect; - size_t len, orig_len; - int failed, data_written, fetch_flags; + struct messageset_context *msgset_ctx; + struct index_mail mail; - /* first see what we need to do. this way we don't first do some - light parsing and later notice that we need to do heavier parsing - anyway */ - if (!fetch_msgcache_open(ctx, rec)) { - /* most likely message not found, just ignore it. */ - imap_msgcache_close(ctx->cache); - ctx->failed = TRUE; - return TRUE; - } - - if (ctx->update_seen && (rec->msg_flags & MAIL_SEEN) == 0) { - (void)index->update_flags(index, rec, idx_seq, - rec->msg_flags | MAIL_SEEN, FALSE); - fetch_flags = TRUE; - } else { - fetch_flags = FALSE; - } - - ctx->str = t_str_new(2048); - - str_printfa(ctx->str, "* %u FETCH (", client_seq); - orig_len = str_len(ctx->str); - - failed = TRUE; - data_written = FALSE; - do { - /* these can't fail */ - if (ctx->fetch_data->uid) - index_fetch_uid(rec, ctx); - if (ctx->fetch_data->flags || fetch_flags) - index_fetch_flags(rec, ctx); + int update_seen; +}; - /* rest can */ - if (ctx->fetch_data->internaldate) - if (!index_fetch_internaldate(rec, ctx)) - break; - if (ctx->fetch_data->body) - if (!index_fetch_body(rec, ctx)) - break; - if (ctx->fetch_data->bodystructure) - if (!index_fetch_bodystructure(rec, ctx)) - break; - if (ctx->fetch_data->envelope) - if (!index_fetch_envelope(rec, ctx)) - break; - if (ctx->fetch_data->rfc822_size) - if (!index_fetch_rfc822_size(rec, ctx)) - break; - - /* send the data written into temp string, - not including the trailing zero */ - ctx->first = str_len(ctx->str) == orig_len; - len = str_len(ctx->str); - if (len > 0) { - if (!ctx->first) - str_truncate(ctx->str, --len); - - if (o_stream_send(ctx->output, - str_c(ctx->str), len) < 0) - break; - } - - data_written = TRUE; - - /* large data */ - if (ctx->fetch_data->rfc822) - if (!index_fetch_send_rfc822(rec, ctx)) - break; - if (ctx->fetch_data->rfc822_text) - if (!index_fetch_send_rfc822_text(rec, ctx)) - break; - if (ctx->fetch_data->rfc822_header) - if (!index_fetch_send_rfc822_header(rec, ctx)) - break; - - sect = ctx->fetch_data->body_sections; - for (; sect != NULL; sect = sect->next) { - if (!index_fetch_body_section(rec, sect, ctx)) - break; - } - - failed = FALSE; - } while (0); - - if (data_written) { - if (o_stream_send(ctx->output, ")\r\n", 3) < 0) - failed = TRUE; - } - - imap_msgcache_close(ctx->cache); - return !failed; -} - -int index_storage_fetch(struct mailbox *box, struct mail_fetch_data *fetch_data, - struct ostream *output, int *all_found) +struct mail_fetch_context * +index_storage_fetch_init(struct mailbox *box, + enum mail_fetch_field wanted_fields, int *update_seen, + const char *messageset, int uidset) { struct index_mailbox *ibox = (struct index_mailbox *) box; - struct fetch_context ctx; - struct mail_fetch_body_data *sect; - int ret; - - memset(&ctx, 0, sizeof(ctx)); + struct mail_fetch_context *ctx; - if (!box->readonly) { - /* If we have any BODY[..] sections, \Seen flag is added for - all messages */ - sect = fetch_data->body_sections; - for (; sect != NULL; sect = sect->next) { - if (!sect->peek) { - ctx.update_seen = TRUE; - break; - } - } + ctx = i_new(struct mail_fetch_context, 1); - if (fetch_data->rfc822 || fetch_data->rfc822_text) - ctx.update_seen = TRUE; - } + if (!box->readonly) + *update_seen = FALSE; /* need exclusive lock to update the \Seen flags */ - if (ctx.update_seen) { + if (*update_seen) { if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE)) - return FALSE; + return NULL; } if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_SHARED)) - return FALSE; + return NULL; - if (ctx.update_seen && + if (*update_seen && ibox->index->header->messages_count == ibox->index->header->seen_messages_count) { /* if all messages are already seen, there's no point in keeping exclusive lock */ - ctx.update_seen = FALSE; + *update_seen = FALSE; (void)index_storage_lock(ibox, MAIL_LOCK_SHARED); } - ctx.box = box; - ctx.storage = box->storage; - ctx.cache = ibox->cache; - ctx.index = ibox->index; - ctx.custom_flags = - mail_custom_flags_list_get(ibox->index->custom_flags); - ctx.custom_flags_count = MAIL_CUSTOM_FLAGS_COUNT; + ctx->ibox = ibox; + ctx->index = ibox->index; + ctx->update_seen = *update_seen; - ctx.fetch_data = fetch_data; - ctx.output = output; + index_mail_init(ibox, &ctx->mail, wanted_fields, NULL); + ctx->msgset_ctx = index_messageset_init(ibox, messageset, uidset); + return ctx; +} - ret = index_messageset_foreach(ibox, fetch_data->messageset, - fetch_data->uidset, - index_fetch_mail, &ctx); +int index_storage_fetch_deinit(struct mail_fetch_context *ctx, int *all_found) +{ + int ret; - if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK)) - return FALSE; + ret = index_messageset_deinit(ctx->msgset_ctx); if (all_found != NULL) - *all_found = ret == 1 && !ctx.failed; + *all_found = ret > 0; + + if (!index_storage_lock(ctx->ibox, MAIL_LOCK_UNLOCK)) + ret = -1; + + if (ctx->ibox->fetch_mail.pool != NULL) + index_mail_deinit(&ctx->ibox->fetch_mail); + index_mail_deinit(&ctx->mail); + i_free(ctx); + return ret >= 0; +} + +struct mail *index_storage_fetch_next(struct mail_fetch_context *ctx) +{ + const struct messageset_mail *msgset_mail; + struct mail_index_record *rec; + int ret; + + do { + msgset_mail = index_messageset_next(ctx->msgset_ctx); + if (msgset_mail == NULL) + return NULL; + + rec = msgset_mail->rec; + ctx->mail.mail.seen_updated = FALSE; + if (ctx->update_seen && (rec->msg_flags & MAIL_SEEN) == 0) { + if (ctx->index->update_flags(ctx->index, rec, + msgset_mail->idx_seq, + rec->msg_flags | MAIL_SEEN, + FALSE)) + ctx->mail.mail.seen_updated = TRUE; + } + + ctx->mail.mail.seq = msgset_mail->client_seq; + ctx->mail.mail.uid = rec->uid; + + ret = index_mail_next(&ctx->mail, rec); + } while (ret == 0); + + return ret < 0 ? NULL : &ctx->mail.mail; +} - return ret > 0; +static struct mail * +fetch_record(struct index_mailbox *ibox, struct mail_index_record *rec, + enum mail_fetch_field wanted_fields) +{ + if (ibox->fetch_mail.pool != NULL) + index_mail_deinit(&ibox->fetch_mail); + + index_mail_init(ibox, &ibox->fetch_mail, wanted_fields, NULL); + if (index_mail_next(&ibox->fetch_mail, rec) <= 0) + return NULL; + + return &ibox->fetch_mail.mail; } + +struct mail *index_storage_fetch_uid(struct mailbox *box, unsigned int uid, + enum mail_fetch_field wanted_fields) +{ + struct index_mailbox *ibox = (struct index_mailbox *) box; + struct mail_index_record *rec; + + i_assert(ibox->index->lock_type != MAIL_LOCK_UNLOCK); + + rec = ibox->index->lookup_uid_range(ibox->index, uid, uid, NULL); + if (rec == NULL) + return NULL; + + return fetch_record(ibox, rec, wanted_fields); +} + +struct mail *index_storage_fetch_seq(struct mailbox *box, unsigned int seq, + enum mail_fetch_field wanted_fields) +{ + struct index_mailbox *ibox = (struct index_mailbox *) box; + struct mail_index_record *rec; + unsigned int expunges_before; + + i_assert(ibox->index->lock_type != MAIL_LOCK_UNLOCK); + + if (mail_modifylog_seq_get_expunges(ibox->index->modifylog, seq, seq, + &expunges_before) == NULL) + return NULL; + + rec = ibox->index->lookup(ibox->index, seq - expunges_before); + if (rec == NULL) + return NULL; + + return fetch_record(ibox, rec, wanted_fields); +} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-fetch.h --- a/src/lib-storage/index/index-fetch.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-fetch.h Mon Jan 20 16:52:51 2003 +0200 @@ -10,7 +10,7 @@ const char **custom_flags; unsigned int custom_flags_count; - struct mail_fetch_data *fetch_data; + //struct mail_fetch_data *fetch_data; struct ostream *output; string_t *str; int update_seen, failed; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-messageset.c --- a/src/lib-storage/index/index-messageset.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-messageset.c Mon Jan 20 16:52:51 2003 +0200 @@ -1,11 +1,87 @@ -/* Copyright (C) 2002 Timo Sirainen */ +/* Copyright (C) 2002-2003 Timo Sirainen */ #include "lib.h" #include "mail-index.h" #include "mail-index-util.h" #include "mail-modifylog.h" +#include "index-storage.h" #include "index-messageset.h" +struct messageset_context { + struct index_mailbox *ibox; + struct mail_index *index; + + const struct modify_log_expunge *expunges; + int expunges_found; + + struct messageset_mail mail; + unsigned int messages_count; + unsigned int num1, num2; + + const char *messageset, *p; + int uidset; + + int first, ret; + const char *error; +}; + +static int uidset_init(struct messageset_context *ctx); +static int seqset_init(struct messageset_context *ctx); + +struct messageset_context * +index_messageset_init(struct index_mailbox *ibox, + const char *messageset, int uidset) +{ + struct messageset_context *ctx; + + i_assert(ibox->index->lock_type != MAIL_LOCK_UNLOCK); + + ctx = i_new(struct messageset_context, 1); + ctx->ibox = ibox; + ctx->index = ibox->index; + ctx->messages_count = ibox->synced_messages_count; + ctx->p = ctx->messageset = messageset; + ctx->uidset = uidset; + + /* Reset index errors, we rely on it to check for failures */ + index_reset_error(ctx->index); + + return ctx; +} + +struct messageset_context * +index_messageset_init_range(struct index_mailbox *ibox, + unsigned int num1, unsigned int num2, int uidset) +{ + struct messageset_context *ctx; + + ctx = index_messageset_init(ibox, NULL, uidset); + ctx->num1 = num1; + ctx->num2 = num2; + return ctx; +} + +int index_messageset_deinit(struct messageset_context *ctx) +{ + int ret = ctx->ret; + + if (ret == 1 && ctx->expunges_found) { + /* some of the messages weren't found */ + ret = 0; + } + + if (ret == -1) + mail_storage_set_index_error(ctx->ibox); + else if (ret == -2) { + /* user error */ + mail_storage_set_syntax_error(ctx->ibox->box.storage, + "%s", ctx->error); + } + + i_free(ctx); + return ret; +} + static unsigned int get_next_number(const char **str) { unsigned int num; @@ -22,321 +98,194 @@ return num; } -static int mail_index_foreach(struct mail_index *index, - unsigned int seq, unsigned int seq2, - msgset_foreach_callback_t callback, void *context) +static int messageset_parse_next(struct messageset_context *ctx) { - struct mail_index_record *rec; - const struct modify_log_expunge *expunges; - unsigned int idx_seq, expunges_before, temp; - int expunges_found; + if (ctx->p == NULL) { + /* num1..num2 already set. */ + ctx->p = ""; + return TRUE; + } + + if (*ctx->p == '*') { + /* last message */ + ctx->num1 = (unsigned int)-1; + ctx->p++; + } else { + ctx->num1 = get_next_number(&ctx->p); + if (ctx->num1 == 0) { + ctx->error = t_strconcat("Invalid messageset: ", + ctx->messageset, NULL); + return FALSE; + } + } + + if (*ctx->p != ':') + ctx->num2 = ctx->num1; + else { + /* first:last range */ + ctx->p++; + + if (*ctx->p == '*') { + ctx->num2 = (unsigned int)-1; + ctx->p++; + } else { + ctx->num2 = get_next_number(&ctx->p); + if (ctx->num2 == 0) { + ctx->error = t_strconcat("Invalid messageset: ", + ctx->messageset, NULL); + return FALSE; + } + } + } + + if (*ctx->p == ',') + ctx->p++; + else if (*ctx->p != '\0') { + ctx->error = t_strdup_printf("Unexpected char '%c' " + "with messageset: %s", + *ctx->p, ctx->messageset); + return FALSE; + } - if (seq > seq2) { - /* swap, as specified by latest IMAP4rev1 spec */ - temp = seq; - seq = seq2; - seq2 = temp; + if (ctx->num1 > ctx->num2) { + /* swap, as specified by latest IMAP4rev1 draft */ + unsigned int temp = ctx->num1; + ctx->num1 = ctx->num2; + ctx->num2 = temp; + } + + return TRUE; +} + +static int uidset_init(struct messageset_context *ctx) +{ + unsigned int expunges_before; + + if (ctx->num1 == (unsigned int)-1) { + struct mail_index_record *rec; + + rec = ctx->index->lookup(ctx->index, ctx->messages_count); + ctx->num1 = rec == NULL ? 0 : rec->uid; } + if (ctx->num2 == (unsigned int)-1) + ctx->num2 = ctx->index->header->next_uid-1; + + /* get list of expunged messages in our range. */ + ctx->expunges = mail_modifylog_uid_get_expunges(ctx->index->modifylog, + ctx->num1, ctx->num2, + &expunges_before); + if (ctx->expunges == NULL) + return -1; + + if (ctx->expunges->uid1 != 0) + ctx->expunges_found = TRUE; + + /* get the first message */ + ctx->mail.rec = ctx->index->lookup_uid_range(ctx->index, + ctx->num1, ctx->num2, + &ctx->mail.idx_seq); + if (ctx->mail.rec == NULL) { + return ctx->index->get_last_error(ctx->index) == + MAIL_INDEX_ERROR_NONE ? 1 : -1; + } + + ctx->mail.client_seq = ctx->mail.idx_seq + expunges_before; + return 0; +} + +static int seqset_init(struct messageset_context *ctx) +{ + unsigned int expunges_before; + + if (ctx->num1 == (unsigned int)-1) + ctx->num1 = ctx->messages_count; + + if (ctx->num2 == (unsigned int)-1) + ctx->num2 = ctx->messages_count; + /* get list of expunged messages in our range. the expunges_before can be used to calculate the current real sequence position */ - expunges = mail_modifylog_seq_get_expunges(index->modifylog, seq, seq2, - &expunges_before); - if (expunges == NULL) + ctx->expunges = mail_modifylog_seq_get_expunges(ctx->index->modifylog, + ctx->num1, ctx->num2, + &expunges_before); + if (ctx->expunges == NULL) return -1; - i_assert(expunges_before < seq); - expunges_found = expunges->uid1 != 0; - - /* Reset index errors, since we later rely on it to check if failed */ - index_reset_error(index); + i_assert(expunges_before < ctx->num1); + if (ctx->expunges->uid1 != 0) + ctx->expunges_found = TRUE; /* get the first non-expunged message. note that if all messages were expunged in the range, this points outside wanted range. */ - idx_seq = seq - expunges_before; - rec = index->lookup(index, idx_seq); - for (; rec != NULL; seq++, idx_seq++) { - /* skip expunged sequences */ - i_assert(rec->uid != 0); - - while (expunges->uid1 != 0 && expunges->uid1 < rec->uid) { - i_assert(expunges->uid2 < rec->uid); - - seq += expunges->seq_count; - expunges++; - } - i_assert(!(expunges->uid1 <= rec->uid && - expunges->uid2 >= rec->uid)); - - if (seq > seq2) - break; - - t_push(); - if (!callback(index, rec, seq, idx_seq, context)) { - t_pop(); - return 0; - } - t_pop(); - - rec = index->next(index, rec); - } - - if (rec == NULL && - index->get_last_error(index) != MAIL_INDEX_ERROR_NONE) { - /* error occured */ - return -1; - } - - return !expunges_found && seq > seq2 ? 1 : 2; -} - -static int mail_index_messageset_foreach(struct mail_index *index, - const char *messageset, - unsigned int messages_count, - msgset_foreach_callback_t callback, - void *context, const char **error) -{ - const char *input; - unsigned int seq, seq2; - int ret, all_found; - - i_assert(index->lock_type != MAIL_LOCK_UNLOCK); - - *error = NULL; - if (messages_count == 0) { - *error = "No messages in mailbox"; - return -2; - } - - all_found = TRUE; - input = messageset; - while (*input != '\0') { - if (*input == '*') { - /* last message */ - seq = messages_count; - input++; - } else { - seq = get_next_number(&input); - if (seq == 0) { - *error = t_strconcat("Invalid messageset: ", - messageset, NULL); - return -2; - } - } - - if (*input != ':') - seq2 = seq; - else { - /* first:last range */ - input++; - - if (*input != '*') { - seq2 = get_next_number(&input); - if (seq2 == 0) { - *error = t_strconcat("Invalid " - "messageset: ", - messageset, NULL); - return -2; - } - } else { - seq2 = messages_count; - input++; - } - } - - if (*input == ',') - input++; - else if (*input != '\0') { - *error = t_strdup_printf("Unexpected char '%c' " - "with messageset: %s", - *input, messageset); - return -2; - } - - if (seq > messages_count || seq2 > messages_count) { - /* non-existent messages requested */ - if (seq <= messages_count) - seq = seq2; - *error = t_strdup_printf("Message sequence %u " - "larger than mailbox size %u", - seq, messages_count); - return -2; - } - - t_push(); - ret = mail_index_foreach(index, seq, seq2, callback, context); - t_pop(); - if (ret <= 0) - return ret; - if (ret == 2) - all_found = FALSE; - } - - return all_found ? 1 : 2; -} - -static int mail_index_uid_foreach(struct mail_index *index, - unsigned int uid, unsigned int uid2, - msgset_foreach_callback_t callback, - void *context) -{ - struct mail_index_record *rec; - const struct modify_log_expunge *expunges; - unsigned int client_seq, idx_seq, expunges_before, temp; - int expunges_found; - - if (uid > uid2) { - /* swap, as specified by latest IMAP4rev1 spec */ - temp = uid; - uid = uid2; - uid2 = temp; + ctx->mail.idx_seq = ctx->num1 - expunges_before; + ctx->mail.rec = ctx->index->lookup(ctx->index, ctx->mail.idx_seq); + if (ctx->mail.rec == NULL) { + return ctx->index->get_last_error(ctx->index) == + MAIL_INDEX_ERROR_NONE ? 1 : -1; } - /* get list of expunged messages in our range. */ - expunges = mail_modifylog_uid_get_expunges(index->modifylog, uid, uid2, - &expunges_before); - if (expunges == NULL) - return -1; - - expunges_found = expunges->uid1 != 0; - - rec = index->lookup_uid_range(index, uid, uid2, &idx_seq); - if (rec == NULL) - return expunges_found ? 2 : 1; - - client_seq = idx_seq + expunges_before; - while (rec != NULL && rec->uid <= uid2) { - while (expunges->uid1 != 0 && expunges->uid1 < rec->uid) { - i_assert(expunges->uid2 < rec->uid); - - client_seq += expunges->seq_count; - expunges++; - } - i_assert(!(expunges->uid1 <= rec->uid && - expunges->uid2 >= rec->uid)); - - t_push(); - if (!callback(index, rec, client_seq, idx_seq, context)) { - t_pop(); - return 0; - } - t_pop(); - - client_seq++; idx_seq++; - rec = index->next(index, rec); - } - - if (rec == NULL && - index->get_last_error(index) != MAIL_INDEX_ERROR_NONE) { - /* error occured */ - return -1; - } - - return expunges_found ? 2 : 1; + ctx->mail.client_seq = ctx->num1; + return 0; } -static int mail_index_uidset_foreach(struct mail_index *index, - const char *uidset, - unsigned int messages_count, - msgset_foreach_callback_t callback, - void *context, const char **error) +const struct messageset_mail * +index_messageset_next(struct messageset_context *ctx) { - struct mail_index_record *rec; - const char *input; - unsigned int uid, uid2; - int ret, all_found; - - i_assert(index->lock_type != MAIL_LOCK_UNLOCK); - - *error = NULL; + struct messageset_mail *mail = &ctx->mail; + int last; - all_found = TRUE; - input = uidset; - while (*input != '\0') { - if (*input == '*') { - /* last message */ - if (messages_count == 0) - uid = 0; - else { - rec = index->lookup(index, messages_count); - uid = rec == NULL ? 0 : rec->uid; - } - input++; - } else { - uid = get_next_number(&input); - if (uid == 0) { - *error = t_strconcat("Invalid uidset: ", - uidset, NULL); - return -2; - } - } + if (ctx->ret != 0) + return NULL; + + if (!ctx->uidset) + last = mail->rec == NULL || mail->client_seq >= ctx->num2; + else + last = mail->rec == NULL || mail->rec->uid >= ctx->num2; - if (*input != ':') - uid2 = uid; - else { - /* first:last range */ - input++; - - if (*input != '*') { - uid2 = get_next_number(&input); - if (uid2 == 0) { - *error = t_strconcat("Invalid uidset: ", - uidset, NULL); - return -2; - } - } else { - uid2 = index->header->next_uid-1; - input++; + if (!last) { + mail->rec = ctx->index->next(ctx->index, mail->rec); + mail->client_seq++; + mail->idx_seq++; + } else { + do { + if (ctx->p != NULL && *ctx->p == '\0') { + /* finished */ + ctx->ret = 1; + return NULL; } - } + + if (!messageset_parse_next(ctx)) { + ctx->ret = -2; + return NULL; + } - if (*input == ',') - input++; - else if (*input != '\0') { - *error = t_strdup_printf("Unexpected char '%c' with " - "uidset: %s", *input, uidset); - return -2; - } + if (ctx->uidset) + ctx->ret = uidset_init(ctx); + else + ctx->ret = seqset_init(ctx); + } while (ctx->ret == 1); - t_push(); - ret = mail_index_uid_foreach(index, uid, uid2, - callback, context); - t_pop(); - if (ret <= 0) - return ret; - if (ret == 2) - all_found = FALSE; + if (ctx->ret != 0) + return NULL; } - return all_found ? 1 : 2; -} + /* fix client_seq */ + while (ctx->expunges->uid1 != 0 && + ctx->expunges->uid1 < mail->rec->uid) { + i_assert(ctx->expunges->uid2 < mail->rec->uid); -int index_messageset_foreach(struct index_mailbox *ibox, - const char *messageset, int uidset, - msgset_foreach_callback_t callback, void *context) -{ - const char *error; - int ret; - - if (uidset) { - ret = mail_index_uidset_foreach(ibox->index, messageset, - ibox->synced_messages_count, - callback, context, &error); - } else { - ret = mail_index_messageset_foreach(ibox->index, messageset, - ibox->synced_messages_count, - callback, context, &error); + mail->client_seq += ctx->expunges->seq_count; + ctx->expunges++; } - if (ret < 0) { - if (ret == -2) { - /* user error */ - mail_storage_set_syntax_error(ibox->box.storage, - "%s", error); - } else { - mail_storage_set_index_error(ibox); - } + i_assert(!(ctx->expunges->uid1 <= mail->rec->uid && + ctx->expunges->uid2 >= mail->rec->uid)); + + if (!ctx->uidset && mail->client_seq > ctx->num2) { + /* finished this set - see if there's more */ + return index_messageset_next(ctx); } - return ret; + return mail; } diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-messageset.h --- a/src/lib-storage/index/index-messageset.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-messageset.h Mon Jan 20 16:52:51 2003 +0200 @@ -1,20 +1,29 @@ #ifndef __INDEX_MESSAGESET_H #define __INDEX_MESSAGESET_H -#include "index-storage.h" +struct index_mailbox; + +struct messageset_mail { + struct mail_index_record *rec; + unsigned int client_seq; + unsigned int idx_seq; +}; + +struct messageset_context; -/* If FALSE is returned, the loop is stopped. */ -typedef int (*msgset_foreach_callback_t)(struct mail_index *index, - struct mail_index_record *rec, - unsigned int client_seq, - unsigned int idx_seq, - void *context); +struct messageset_context * +index_messageset_init(struct index_mailbox *ibox, + const char *messageset, int uidset); -/* Returns 1 if all were found, 2 if some messages were deleted, - 0 callback returned FALSE, -1 if internal error occured or -2 if messageset - was invalid. */ -int index_messageset_foreach(struct index_mailbox *ibox, - const char *messageset, int uidset, - msgset_foreach_callback_t callback, void *context); +struct messageset_context * +index_messageset_init_range(struct index_mailbox *ibox, + unsigned int num1, unsigned int num2, int uidset); + +/* Returns 1 if all were found, 0 if some messages were deleted, + -1 if internal error occured or -2 if messageset was invalid. */ +int index_messageset_deinit(struct messageset_context *ctx); + +const struct messageset_mail * +index_messageset_next(struct messageset_context *ctx); #endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-msgcache.c --- a/src/lib-storage/index/index-msgcache.c Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -#include "lib.h" -#include "istream.h" -#include "imap-date.h" -#include "imap-message-cache.h" -#include "message-part-serialize.h" -#include "mail-index.h" -#include "mail-index-util.h" -#include "index-storage.h" - -#include - -struct index_msgcache_context { - struct mail_index *index; - struct mail_index_record *rec; - time_t internal_date; -}; - -int index_msgcache_open(struct imap_message_cache *cache, - struct mail_index *index, struct mail_index_record *rec, - enum imap_cache_field fields) -{ - struct index_msgcache_context *ctx; - uoff_t vp_header_size, vp_body_size, full_virtual_size; - const uoff_t *uoff_p; - size_t size; - - ctx = t_new(struct index_msgcache_context, 1); - ctx->index = index; - ctx->rec = rec; - ctx->internal_date = (time_t)-1; - - full_virtual_size = (uoff_t)-1; - vp_header_size = (uoff_t)-1; - vp_body_size = (uoff_t)-1; - - if ((ctx->rec->index_flags & INDEX_MAIL_FLAG_BINARY_HEADER)) { - uoff_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec, - DATA_HDR_HEADER_SIZE, - &size); - if (uoff_p != NULL) { - i_assert(size == sizeof(*uoff_p)); - vp_header_size = *uoff_p; - } - } - - if ((ctx->rec->index_flags & INDEX_MAIL_FLAG_BINARY_BODY)) { - uoff_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec, - DATA_HDR_BODY_SIZE, - &size); - if (uoff_p != NULL) { - i_assert(size == sizeof(*uoff_p)); - vp_body_size = *uoff_p; - } - } - - if (vp_header_size != (uoff_t)-1 && vp_body_size != (uoff_t)-1) - full_virtual_size = vp_header_size + vp_body_size; - else { - uoff_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec, - DATA_HDR_VIRTUAL_SIZE, - &size); - if (uoff_p != NULL) { - i_assert(size == sizeof(*uoff_p)); - full_virtual_size = *uoff_p; - } - } - - return imap_msgcache_open(cache, rec->uid, fields, - vp_header_size, vp_body_size, - full_virtual_size, ctx); -} - -static struct istream *index_msgcache_open_mail(void *context) -{ - struct index_msgcache_context *ctx = context; - int deleted; - - return ctx->index->open_mail(ctx->index, ctx->rec, - &ctx->internal_date, &deleted); -} - -static struct istream * -index_msgcache_stream_rewind(struct istream *input, - void *context __attr_unused__) -{ - i_stream_seek(input, 0); - return input; -} - -static const char * -index_msgcache_get_cached_field(enum imap_cache_field field, void *context) -{ - struct index_msgcache_context *ctx = context; - enum mail_data_field data_field; - const time_t *time_p; - const char *ret; - size_t size; - - switch (field) { - case IMAP_CACHE_INTERNALDATE: - if (ctx->internal_date != (time_t)-1) - return imap_to_datetime(ctx->internal_date); - - time_p = ctx->index->lookup_field_raw(ctx->index, ctx->rec, - DATA_HDR_INTERNAL_DATE, - &size); - if (time_p == NULL) { - i_assert(size == sizeof(*time_p)); - return imap_to_datetime(*time_p); - } else { - ctx->index->cache_fields_later(ctx->index, - DATA_HDR_INTERNAL_DATE); - return NULL; - } - case IMAP_CACHE_BODY: - data_field = DATA_FIELD_BODY; - break; - case IMAP_CACHE_BODYSTRUCTURE: - data_field = DATA_FIELD_BODYSTRUCTURE; - break; - case IMAP_CACHE_ENVELOPE: - data_field = DATA_FIELD_ENVELOPE; - break; - default: - return NULL; - } - - ret = ctx->index->lookup_field(ctx->index, ctx->rec, data_field); - if (ret == NULL) - ctx->index->cache_fields_later(ctx->index, data_field); - return ret; -} - -static struct message_part * -index_msgcache_get_cached_parts(pool_t pool, void *context) -{ - struct index_msgcache_context *ctx = context; - struct message_part *part; - const void *part_data; - const char *error; - size_t part_size; - - part_data = ctx->index->lookup_field_raw(ctx->index, ctx->rec, - DATA_FIELD_MESSAGEPART, - &part_size); - if (part_data == NULL) { - ctx->index->cache_fields_later(ctx->index, - DATA_FIELD_MESSAGEPART); - return NULL; - } - - part = message_part_deserialize(pool, part_data, part_size, &error); - if (part == NULL) { - index_set_corrupted(ctx->index, - "Corrupted cached message_part data (%s)", error); - return NULL; - } - - return part; -} - -static time_t index_msgcache_get_internal_date(void *context) -{ - struct index_msgcache_context *ctx = context; - - if (ctx->internal_date != (time_t)-1) - return ctx->internal_date; - - return ctx->index->get_internal_date(ctx->index, ctx->rec); -} - -struct imap_message_cache_iface index_msgcache_iface = { - index_msgcache_open_mail, - index_msgcache_stream_rewind, - index_msgcache_get_cached_field, - index_msgcache_get_cached_parts, - index_msgcache_get_internal_date -}; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-search.c --- a/src/lib-storage/index/index-search.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-search.c Mon Jan 20 16:52:51 2003 +0200 @@ -2,21 +2,19 @@ #include "lib.h" #include "istream.h" -#include "ostream.h" -#include "mmap-util.h" +#include "str.h" +#include "message-address.h" #include "message-date.h" -#include "message-size.h" #include "message-body-search.h" #include "message-header-search.h" #include "imap-date.h" #include "imap-envelope.h" #include "index-storage.h" -#include "index-sort.h" -#include "mail-index-util.h" +#include "index-messageset.h" +#include "index-mail.h" +#include "mail-custom-flags.h" #include "mail-modifylog.h" -#include "mail-custom-flags.h" #include "mail-search.h" -#include "mail-thread.h" #include #include @@ -29,24 +27,23 @@ #define TXT_UNKNOWN_CHARSET "[BADCHARSET] Unknown charset" #define TXT_INVALID_SEARCH_KEY "Invalid search key" -struct search_index_context { - pool_t hdr_pool; +struct mail_search_context { struct index_mailbox *ibox; - struct mail_index_record *rec; - unsigned int client_seq; - const char *charset; + char *charset; + struct mail_search_arg *args; + + struct messageset_context *msgset_ctx; + struct index_mail imail; + struct mail *mail; + + pool_t hdr_pool; const char *error; - unsigned int cached:1; - unsigned int threading:1; - - /* for threading: */ - const char *message_id, *in_reply_to, *references; - time_t sent_date; + int failed; }; struct search_header_context { - struct search_index_context *index_context; + struct mail_search_context *index_context; struct mail_search_arg *args; const unsigned char *name, *value; @@ -57,13 +54,11 @@ }; struct search_body_context { - struct search_index_context *index_ctx; + struct mail_search_context *index_ctx; struct istream *input; - struct message_part *part; + const struct message_part *part; }; -static enum mail_sort_type sort_unsorted[] = { MAIL_SORT_END }; - static int msgset_contains(const char *set, unsigned int match_num, unsigned int max_num) { @@ -175,7 +170,7 @@ { switch (type) { case SEARCH_ALL: - return TRUE; + return 1; case SEARCH_SET: return msgset_contains(value, client_seq, ibox->synced_messages_count); @@ -206,9 +201,10 @@ static void search_index_arg(struct mail_search_arg *arg, void *context) { - struct search_index_context *ctx = context; + struct mail_search_context *ctx = context; - switch (search_arg_match_index(ctx->ibox, ctx->rec, ctx->client_seq, + switch (search_arg_match_index(ctx->ibox, ctx->imail.data.rec, + ctx->mail->seq, arg->type, arg->value.str)) { case -1: /* unknown */ @@ -222,19 +218,8 @@ } } -static struct imap_message_cache * -search_open_cache(struct search_index_context *ctx) -{ - if (!ctx->cached) { - (void)index_msgcache_open(ctx->ibox->cache, - ctx->ibox->index, ctx->rec, 0); - ctx->cached = TRUE; - } - return ctx->ibox->cache; -} - /* Returns >0 = matched, 0 = not matched, -1 = unknown */ -static int search_arg_match_cached(struct search_index_context *ctx, +static int search_arg_match_cached(struct mail_search_context *ctx, enum mail_search_arg_type type, const char *value) { @@ -246,8 +231,7 @@ case SEARCH_BEFORE: case SEARCH_ON: case SEARCH_SINCE: - internal_date = imap_msgcache_get_internal_date( - search_open_cache(ctx)); + internal_date = ctx->mail->get_received_date(ctx->mail); if (internal_date == (time_t)-1) return -1; @@ -270,8 +254,7 @@ /* sizes */ case SEARCH_SMALLER: case SEARCH_LARGER: - virtual_size = imap_msgcache_get_virtual_size( - search_open_cache(ctx)); + virtual_size = ctx->mail->get_size(ctx->mail); if (virtual_size == (uoff_t)-1) return -1; @@ -288,7 +271,7 @@ static void search_cached_arg(struct mail_search_arg *arg, void *context) { - struct search_index_context *ctx = context; + struct mail_search_context *ctx = context; switch (search_arg_match_cached(ctx, arg->type, arg->value.str)) { @@ -318,7 +301,8 @@ /* NOTE: Latest IMAP4rev1 draft specifies that timezone is ignored in searches. sent_time is returned as UTC, so change it. */ - if (!message_date_parse(sent_value, &sent_time, &timezone_offset)) + if (!message_date_parse((const unsigned char *) sent_value, (size_t)-1, + &sent_time, &timezone_offset)) return 0; sent_time -= timezone_offset * 60; @@ -336,7 +320,7 @@ } static struct header_search_context * -search_header_context(struct search_index_context *ctx, +search_header_context(struct mail_search_context *ctx, struct mail_search_arg *arg) { int unknown_charset; @@ -363,7 +347,7 @@ } /* Returns >0 = matched, 0 = not matched, -1 = unknown */ -static int search_arg_match_envelope(struct search_index_context *ctx, +static int search_arg_match_envelope(struct mail_search_context *ctx, struct mail_search_arg *arg) { struct mail_index *index = ctx->ibox->index; @@ -408,7 +392,8 @@ t_push(); /* get field from hopefully cached envelope */ - envelope = index->lookup_field(index, ctx->rec, DATA_FIELD_ENVELOPE); + envelope = index->lookup_field(index, ctx->imail.data.rec, + DATA_FIELD_ENVELOPE); if (envelope != NULL) { ret = imap_envelope_parse(envelope, env_field, IMAP_ENVELOPE_RESULT_TYPE_STRING, @@ -457,7 +442,7 @@ static void search_envelope_arg(struct mail_search_arg *arg, void *context) { - struct search_index_context *ctx = context; + struct mail_search_context *ctx = context; switch (search_arg_match_envelope(ctx, arg)) { case -1: @@ -534,13 +519,24 @@ } else { t_push(); - /* then check if the value matches */ hdr_search_ctx = search_header_context(ctx->index_context, arg); if (hdr_search_ctx == NULL) ret = 0; - else { - len = ctx->value_len; - ret = message_header_search(ctx->value, len, + else if (arg->type == SEARCH_FROM || arg->type == SEARCH_TO || + arg->type == SEARCH_CC || arg->type == SEARCH_BCC) { + /* we have to match against normalized address */ + struct message_address *addr; + string_t *str; + + addr = message_address_parse(data_stack_pool, + ctx->value, ctx->value_len, + 0); + str = t_str_new(ctx->value_len); + message_address_write(str, addr); + ret = message_header_search(str_data(str), str_len(str), + hdr_search_ctx) ? 1 : 0; + } else { + ret = message_header_search(ctx->value, ctx->value_len, hdr_search_ctx) ? 1 : 0; } t_pop(); @@ -549,34 +545,15 @@ ARG_SET_RESULT(arg, ret); } -static void search_header(struct message_part *part __attr_unused__, +static void search_header(struct message_part *part, const unsigned char *name, size_t name_len, const unsigned char *value, size_t value_len, void *context) { struct search_header_context *ctx = context; - int timezone_offset; - if (ctx->threading) { - struct search_index_context *ictx = ctx->index_context; - - if (name_len == 10 && memcasecmp(name, "Message-ID", 10) == 0) - ictx->message_id = t_strndup(value, value_len); - else if (name_len == 11 && - memcasecmp(name, "In-Reply-To", 11) == 0) - ictx->in_reply_to = t_strndup(value, value_len); - else if (name_len == 10 && - memcasecmp(name, "References", 10) == 0) - ictx->references = t_strndup(value, value_len); - else if (name_len == 4 && memcasecmp(name, "Date", 4) == 0) { - t_push(); - if (!message_date_parse(t_strndup(value, value_len), - &ictx->sent_date, - &timezone_offset)) - ictx->sent_date = 0; - t_pop(); - } - } + index_mail_parse_header(part, name, name_len, value, value_len, + ctx->index_context->mail); if ((ctx->custom_header && name_len > 0) || (name_len == 4 && memcasecmp(name, "Date", 4) == 0) || @@ -620,36 +597,39 @@ } static int search_arg_match_text(struct mail_search_arg *args, - struct search_index_context *ctx) + struct mail_search_context *ctx) { struct istream *input; int have_headers, have_body, have_text; /* first check what we need to use */ mail_search_args_analyze(args, &have_headers, &have_body, &have_text); - if (ctx->threading) - have_headers = TRUE; if (!have_headers && !have_body && !have_text) return TRUE; if (have_headers || have_text) { struct search_header_context hdr_ctx; - if (!imap_msgcache_get_data(search_open_cache(ctx), &input)) + input = ctx->mail->get_stream(ctx->mail, NULL, NULL); + if (input == NULL) return FALSE; memset(&hdr_ctx, 0, sizeof(hdr_ctx)); hdr_ctx.index_context = ctx; hdr_ctx.custom_header = TRUE; hdr_ctx.args = args; - hdr_ctx.threading = ctx->threading; + index_mail_init_parse_header(&ctx->imail); message_parse_header(NULL, input, NULL, search_header, &hdr_ctx); } else { - if (!imap_msgcache_get_rfc822(search_open_cache(ctx), &input, - NULL, NULL)) + struct message_size hdr_size; + + input = ctx->mail->get_stream(ctx->mail, &hdr_size, NULL); + if (input == NULL) return FALSE; + + i_stream_seek(input, hdr_size.physical_size); } if (have_text || have_body) { @@ -658,7 +638,7 @@ memset(&body_ctx, 0, sizeof(body_ctx)); body_ctx.index_ctx = ctx; body_ctx.input = input; - body_ctx.part = imap_msgcache_get_parts(search_open_cache(ctx)); + body_ctx.part = ctx->mail->get_parts(ctx->mail); mail_search_args_foreach(args, search_body, &body_ctx); } @@ -706,7 +686,7 @@ } static int search_get_sequid(struct index_mailbox *ibox, - struct mail_search_arg *args, + const struct mail_search_arg *args, unsigned int *first_seq, unsigned int *last_seq, unsigned int *first_uid, unsigned int *last_uid) { @@ -744,7 +724,7 @@ } static int search_limit_by_flags(struct index_mailbox *ibox, - struct mail_search_arg *args, + const struct mail_search_arg *args, unsigned int *first_uid, unsigned int *last_uid) { @@ -809,8 +789,10 @@ return FALSE; } - (void)mail_modifylog_seq_get_expunges(ibox->index->modifylog, seq, seq, - &expunges_before); + if (mail_modifylog_seq_get_expunges(ibox->index->modifylog, seq, seq, + &expunges_before) == NULL) + return FALSE; + seq -= expunges_before; rec = ibox->index->lookup(ibox->index, seq); @@ -819,7 +801,7 @@ } static int search_get_uid_range(struct index_mailbox *ibox, - struct mail_search_arg *args, + const struct mail_search_arg *args, unsigned int *first_uid, unsigned int *last_uid) { unsigned int first_seq, last_seq, uid; @@ -868,180 +850,143 @@ return 1; } -static int search_messages(struct index_mailbox *ibox, const char *charset, - struct mail_search_arg *args, - struct mail_sort_context *sort_ctx, - struct mail_thread_context *thread_ctx, - struct index_sort_context *index_sort_ctx, - struct ostream *output, int uid_result) +int index_storage_search_get_sorting(struct mailbox *box __attr_unused__, + enum mail_sort_type *sort_program) +{ + /* currently we don't support sorting */ + *sort_program = MAIL_SORT_END; + return TRUE; +} + +struct mail_search_context * +index_storage_search_init(struct mailbox *box, const char *charset, + struct mail_search_arg *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + const char *const wanted_headers[]) { - struct search_index_context ctx; - struct mail_index_record *rec; - struct mail_search_arg *arg; - const struct modify_log_expunge *expunges; - unsigned int first_uid, last_uid, client_seq, expunges_before; - const char *str; - int found, failed; + struct index_mailbox *ibox = (struct index_mailbox *) box; + struct mail_search_context *ctx; + unsigned int first_uid, last_uid; + + if (sort_program != NULL && *sort_program != MAIL_SORT_END) { + i_error("BUG: index_storage_search_init(): " + "invalid sort_program"); + return NULL; + } + + if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_SHARED)) + return NULL; + + ctx = i_new(struct mail_search_context, 1); + ctx->ibox = ibox; + ctx->charset = i_strdup(charset); + ctx->args = args; + + ctx->mail = (struct mail *) &ctx->imail; + index_mail_init(ibox, &ctx->imail, wanted_fields, wanted_headers); if (ibox->synced_messages_count == 0) - return TRUE; + return ctx; /* see if we can limit the records we look at */ switch (search_get_uid_range(ibox, args, &first_uid, &last_uid)) { case -1: /* error */ - return FALSE; + ctx->failed = TRUE; + return ctx; case 0: /* nothing found */ - return TRUE; + return ctx; + } + + ctx->msgset_ctx = + index_messageset_init_range(ibox, first_uid, last_uid, TRUE); + return ctx; +} + +int index_storage_search_deinit(struct mail_search_context *ctx) +{ + int ret; + + ret = !ctx->failed && ctx->error == NULL; + + if (ctx->msgset_ctx != NULL) { + if (index_messageset_deinit(ctx->msgset_ctx) < 0) + ret = FALSE; + } + + if (!index_storage_lock(ctx->ibox, MAIL_LOCK_UNLOCK)) + ret = FALSE; + + if (ctx->error != NULL) { + mail_storage_set_error(ctx->ibox->box.storage, + "%s", ctx->error); } - rec = ibox->index->lookup_uid_range(ibox->index, first_uid, last_uid, - &client_seq); - if (rec == NULL) - return TRUE; + if (ctx->hdr_pool != NULL) + pool_unref(ctx->hdr_pool); + + if (ctx->ibox->fetch_mail.pool != NULL) + index_mail_deinit(&ctx->ibox->fetch_mail); + index_mail_deinit(&ctx->imail); + i_free(ctx); + return ret; +} - expunges = mail_modifylog_uid_get_expunges(ibox->index->modifylog, - rec->uid, last_uid, - &expunges_before); - client_seq += expunges_before; - index_sort_ctx->synced_sequences = expunges->uid1 == 0; +struct mail *index_storage_search_next(struct mail_search_context *ctx) +{ + const struct messageset_mail *msgset_mail; + struct mail_search_arg *arg; + int found, ret; - memset(&ctx, 0, sizeof(ctx)); - ctx.ibox = ibox; - ctx.charset = charset; - ctx.threading = thread_ctx != NULL; + if (ctx->msgset_ctx == NULL) { + /* initialization failed or didn't found any messages */ + return NULL; + } - for (; rec != NULL && rec->uid <= last_uid; client_seq++) { - while (expunges->uid1 != 0 && expunges->uid1 < rec->uid) { - i_assert(expunges->uid2 < rec->uid); + do { + msgset_mail = index_messageset_next(ctx->msgset_ctx); + if (msgset_mail == NULL) + return NULL; - client_seq += expunges->seq_count; - expunges++; - } - i_assert(!(expunges->uid1 <= rec->uid && - expunges->uid2 >= rec->uid)); + ctx->mail->seq = msgset_mail->client_seq; + ctx->mail->uid = msgset_mail->rec->uid; + ret = index_mail_next(&ctx->imail, msgset_mail->rec); - ctx.rec = rec; - ctx.client_seq = client_seq; - ctx.cached = FALSE; + if (ret < 0) + return NULL; - ctx.message_id = ctx.in_reply_to = ctx.references = NULL; + if (ret == 0) + found = FALSE; + else { + mail_search_args_reset(ctx->args); - mail_search_args_reset(args); - - t_push(); + t_push(); - mail_search_args_foreach(args, search_index_arg, &ctx); - mail_search_args_foreach(args, search_cached_arg, &ctx); - mail_search_args_foreach(args, search_envelope_arg, &ctx); - failed = !search_arg_match_text(args, &ctx); - imap_msgcache_close(ibox->cache); + mail_search_args_foreach(ctx->args, search_index_arg, + ctx); + mail_search_args_foreach(ctx->args, search_cached_arg, + ctx); + mail_search_args_foreach(ctx->args, search_envelope_arg, + ctx); + found = search_arg_match_text(ctx->args, ctx); - if (ctx.error != NULL) { t_pop(); - break; + + if (ctx->error != NULL) + return NULL; } - if (!failed) { - found = TRUE; - for (arg = args; arg != NULL; arg = arg->next) { + if (found) { + for (arg = ctx->args; arg != NULL; arg = arg->next) { if (arg->result != 1) { found = FALSE; break; } } - - if (found) { - unsigned int id = uid_result ? - rec->uid : client_seq; - - index_sort_ctx->current_client_seq = client_seq; - index_sort_ctx->current_rec = rec; - - if (sort_ctx != NULL) - mail_sort_input(sort_ctx, id); - else if (thread_ctx != NULL) { - mail_thread_input(thread_ctx, id, - ctx.message_id, - ctx.in_reply_to, - ctx.references, - ctx.sent_date); - } else { - o_stream_send(output, " ", 1); - - str = dec2str(id); - o_stream_send_str(output, str); - } - } } - t_pop(); - - rec = ibox->index->next(ibox->index, rec); - } - - if (ctx.hdr_pool != NULL) - pool_unref(ctx.hdr_pool); - - if (ctx.error != NULL) - mail_storage_set_error(ibox->box.storage, "%s", ctx.error); - return ctx.error == NULL; -} + } while (!found); -int index_storage_search(struct mailbox *box, const char *charset, - struct mail_search_arg *args, - enum mail_sort_type *sorting, - enum mail_thread_type threading, - struct ostream *output, int uid_result) -{ - struct index_mailbox *ibox = (struct index_mailbox *) box; - struct mail_sort_context *sort_ctx; - struct mail_thread_context *thread_ctx; - struct index_sort_context index_sort_ctx; - int failed; - - if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_SHARED)) - return FALSE; - - if (sorting != NULL) { - memset(&index_sort_ctx, 0, sizeof(index_sort_ctx)); - index_sort_ctx.ibox = ibox; - index_sort_ctx.output = output; - index_sort_ctx.id_is_uid = uid_result; - - thread_ctx = NULL; - sort_ctx = mail_sort_init(sort_unsorted, sorting, output, - &index_sort_callbacks, - &index_sort_ctx); - o_stream_send_str(output, "* SORT"); - } else if (threading != MAIL_THREAD_NONE) { - memset(&index_sort_ctx, 0, sizeof(index_sort_ctx)); - index_sort_ctx.ibox = ibox; - index_sort_ctx.id_is_uid = uid_result; - - sort_ctx = NULL; - thread_ctx = mail_thread_init(threading, output, - &index_sort_callbacks, - &index_sort_ctx); - o_stream_send_str(output, "* THREAD"); - } else { - memset(&index_sort_ctx, 0, sizeof(index_sort_ctx)); - sort_ctx = NULL; - thread_ctx = NULL; - o_stream_send_str(output, "* SEARCH"); - } - - failed = !search_messages(ibox, charset, args, sort_ctx, thread_ctx, - &index_sort_ctx, output, uid_result); - if (sort_ctx != NULL) - mail_sort_deinit(sort_ctx); - if (thread_ctx != NULL) - mail_thread_finish(thread_ctx); - - o_stream_send(output, "\r\n", 2); - - if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK)) - return FALSE; - - return !failed; + return ctx->mail; } diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-sort.c --- a/src/lib-storage/index/index-sort.c Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -#include "lib.h" -#include "ostream.h" -#include "message-date.h" -#include "imap-envelope.h" -#include "imap-message-cache.h" -#include "mail-index.h" -#include "mail-modifylog.h" -#include "index-storage.h" -#include "index-sort.h" - -static struct mail_index_record * -lookup_client_seq(struct index_sort_context *ctx, unsigned int client_seq) -{ - struct mail_index_record *rec; - unsigned int expunges_before; - - if (ctx->synced_sequences) - return ctx->ibox->index->lookup(ctx->ibox->index, client_seq); - - t_push(); - if (mail_modifylog_seq_get_expunges(ctx->ibox->index->modifylog, - client_seq, client_seq, - &expunges_before) == NULL) { - rec = NULL; - } else { - rec = ctx->ibox->index->lookup(ctx->ibox->index, - client_seq - expunges_before); - } - t_pop(); - - return rec; -} - -static struct imap_message_cache * -search_open_cache(struct index_sort_context *ctx, unsigned int id) -{ - i_assert(id != 0); - - if (ctx->last_id != id) { - ctx->cached = FALSE; - ctx->last_id = id; - - if ((ctx->id_is_uid && ctx->current_rec->uid == id) || - (!ctx->id_is_uid && ctx->current_client_seq == id)) { - ctx->rec = ctx->current_rec; - } else if (ctx->id_is_uid) { - ctx->rec = ctx->ibox->index-> - lookup_uid_range(ctx->ibox->index, - id, id, NULL); - } else { - ctx->rec = lookup_client_seq(ctx, id); - } - - if (ctx->rec == NULL) { - ctx->last_id = 0; - return NULL; - } - } - - if (!ctx->cached) { - ctx->cached = TRUE; - (void)index_msgcache_open(ctx->ibox->cache, - ctx->ibox->index, ctx->rec, - IMAP_CACHE_ENVELOPE); - } - - return ctx->ibox->cache; -} - -static uoff_t _input_uofft(enum mail_sort_type type, - unsigned int id, void *context) -{ - struct index_sort_context *ctx = context; - struct imap_message_cache *cache; - - if (type != MAIL_SORT_SIZE) { - i_unreached(); - return 0; - } - - cache = search_open_cache(ctx, id); - return cache == NULL ? 0 : imap_msgcache_get_virtual_size(cache); -} - -static const char *_input_mailbox(enum mail_sort_type type, unsigned int id, - void *context) -{ - struct index_sort_context *ctx = context; - enum imap_envelope_field env_field; - const char *envelope, *str; - - switch (type) { - case MAIL_SORT_CC: - env_field = IMAP_ENVELOPE_CC; - break; - case MAIL_SORT_FROM: - env_field = IMAP_ENVELOPE_FROM; - break; - case MAIL_SORT_TO: - env_field = IMAP_ENVELOPE_TO; - break; - default: - i_unreached(); - return NULL; - } - - /* get field from hopefully cached envelope */ - envelope = imap_msgcache_get(search_open_cache(ctx, id), - IMAP_CACHE_ENVELOPE); - if (envelope == NULL) - return NULL; - - if (!imap_envelope_parse(envelope, env_field, - IMAP_ENVELOPE_RESULT_TYPE_FIRST_MAILBOX, &str)) - return NULL; - - return str; -} - -static const char *_input_str(enum mail_sort_type type, - unsigned int id, void *context) -{ - struct index_sort_context *ctx = context; - enum imap_envelope_field env_field; - const char *envelope, *str; - - switch (type) { - case MAIL_SORT_DATE: - env_field = IMAP_ENVELOPE_DATE; - break; - case MAIL_SORT_SUBJECT: - env_field = IMAP_ENVELOPE_SUBJECT; - break; - default: - i_unreached(); - return NULL; - } - - /* get field from hopefully cached envelope */ - envelope = imap_msgcache_get(search_open_cache(ctx, id), - IMAP_CACHE_ENVELOPE); - if (envelope == NULL) - return NULL; - - if (!imap_envelope_parse(envelope, env_field, - IMAP_ENVELOPE_RESULT_TYPE_STRING, &str)) - return NULL; - - return str; -} - -static time_t _input_time(enum mail_sort_type type, - unsigned int id, void *context) -{ - struct index_sort_context *ctx = context; - struct imap_message_cache *cache; - const char *str; - time_t time; - int timezone_offset; - - switch (type) { - case MAIL_SORT_ARRIVAL: - cache = search_open_cache(ctx, id); - return cache == NULL ? 0 : - imap_msgcache_get_internal_date(cache); - case MAIL_SORT_DATE: - str = _input_str(type, id, context); - if (str == NULL) - return 0; - - if (!message_date_parse(str, &time, &timezone_offset)) - return 0; - - return time; - default: - i_unreached(); - return 0; - } -} - -static void _input_reset(void *context) -{ - struct index_sort_context *ctx = context; - - ctx->cached = FALSE; -} - -struct mail_sort_callbacks index_sort_callbacks = { - _input_time, - _input_uofft, - _input_mailbox, - _input_str, - _input_reset -}; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-sort.h --- a/src/lib-storage/index/index-sort.h Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -#ifndef __INDEX_SORT_H -#define __INDEX_SORT_H - -#include "mail-storage.h" -#include "mail-sort.h" - -struct index_sort_context { - struct index_mailbox *ibox; - struct ostream *output; - - unsigned int current_client_seq; - struct mail_index_record *current_rec; - - unsigned int last_id; - struct mail_index_record *rec; - - unsigned int cached:1; - unsigned int id_is_uid:1; - unsigned int synced_sequences:1; -}; - -extern struct mail_sort_callbacks index_sort_callbacks; - -#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-storage.c --- a/src/lib-storage/index/index-storage.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-storage.c Mon Jan 20 16:52:51 2003 +0200 @@ -227,7 +227,6 @@ ibox->box.allow_custom_flags = TRUE; ibox->index = index; - ibox->cache = imap_msgcache_alloc(&index_msgcache_iface); ibox->next_lock_notify = time(NULL) + LOCK_NOTIFY_INTERVAL; index->set_lock_notify_callback(index, lock_notify, ibox); @@ -266,7 +265,6 @@ struct index_mailbox *ibox = (struct index_mailbox *) box; index_mailbox_check_remove(ibox); - imap_msgcache_free(ibox->cache); if (ibox->index != NULL) index_storage_unref(ibox->index); @@ -316,13 +314,14 @@ int index_mailbox_fix_custom_flags(struct index_mailbox *ibox, enum mail_flags *flags, - const char *custom_flags[]) + const char *custom_flags[], + unsigned int custom_flags_count) { int ret; ret = mail_custom_flags_fix_list(ibox->index->custom_flags, flags, custom_flags, - MAIL_CUSTOM_FLAGS_COUNT); + custom_flags_count); switch (ret) { case 1: return TRUE; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-storage.h --- a/src/lib-storage/index/index-storage.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-storage.h Mon Jan 20 16:52:51 2003 +0200 @@ -3,7 +3,7 @@ #include "mail-storage.h" #include "mail-index.h" -#include "imap-message-cache.h" +#include "index-mail.h" struct index_mailbox { struct mailbox box; @@ -13,13 +13,13 @@ int (*expunge_locked)(struct index_mailbox *ibox, int notify); struct mail_index *index; - struct imap_message_cache *cache; char *check_path; struct timeout *check_to; time_t check_file_stamp; time_t last_check; + struct index_mail fetch_mail; /* fetch_uid() or fetch_seq() */ unsigned int synced_messages_count; time_t next_lock_notify; /* temporary */ @@ -28,8 +28,6 @@ unsigned int delay_save_unlocking:1; /* For COPYing inside mailbox */ }; -extern struct imap_message_cache_iface index_msgcache_iface; - int mail_storage_set_index_error(struct index_mailbox *ibox); void index_storage_init_lock_notify(struct index_mailbox *ibox); int index_storage_lock(struct index_mailbox *ibox, @@ -51,7 +49,8 @@ int index_mailbox_fix_custom_flags(struct index_mailbox *ibox, enum mail_flags *flags, - const char *custom_flags[]); + const char *custom_flags[], + unsigned int custom_flags_count); unsigned int index_storage_get_recent_count(struct mail_index *index); @@ -65,10 +64,6 @@ struct istream *input, struct ostream *output, uoff_t data_size); -int index_msgcache_open(struct imap_message_cache *cache, - struct mail_index *index, struct mail_index_record *rec, - enum imap_cache_field fields); - void index_mailbox_check_add(struct index_mailbox *ibox, const char *path); void index_mailbox_check_remove(struct index_mailbox *ibox); @@ -84,16 +79,31 @@ struct mailbox_status *status); int index_storage_sync(struct mailbox *box, int sync_expunges); int index_storage_update_flags(struct mailbox *box, const char *messageset, - int uidset, enum mail_flags flags, - const char *custom_flags[], + int uidset, const struct mail_full_flags *flags, enum modify_type modify_type, int notify, int *all_found); -int index_storage_fetch(struct mailbox *box, struct mail_fetch_data *fetch_data, - struct ostream *output, int *all_found); -int index_storage_search(struct mailbox *box, const char *charset, - struct mail_search_arg *args, - enum mail_sort_type *sorting, - enum mail_thread_type threading, - struct ostream *output, int uid_result); + +struct mail_fetch_context * +index_storage_fetch_init(struct mailbox *box, + enum mail_fetch_field wanted_fields, int *update_seen, + const char *messageset, int uidset); +int index_storage_fetch_deinit(struct mail_fetch_context *ctx, int *all_found); +struct mail *index_storage_fetch_next(struct mail_fetch_context *ctx); + +struct mail *index_storage_fetch_uid(struct mailbox *box, unsigned int uid, + enum mail_fetch_field wanted_fields); +struct mail *index_storage_fetch_seq(struct mailbox *box, unsigned int seq, + enum mail_fetch_field wanted_fields); + +int index_storage_search_get_sorting(struct mailbox *box, + enum mail_sort_type *sort_program); +struct mail_search_context * +index_storage_search_init(struct mailbox *box, const char *charset, + struct mail_search_arg *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + const char *const wanted_headers[]); +int index_storage_search_deinit(struct mail_search_context *ctx); +struct mail *index_storage_search_next(struct mail_search_context *ctx); #endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/index-update-flags.c --- a/src/lib-storage/index/index-update-flags.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/index-update-flags.c Mon Jan 20 16:52:51 2003 +0200 @@ -5,78 +5,76 @@ #include "index-messageset.h" #include "mail-custom-flags.h" -struct update_context { - struct index_mailbox *ibox; - enum mail_flags flags; - enum modify_type modify_type; - int notify; -}; - -static int update_cb(struct mail_index *index, struct mail_index_record *rec, - unsigned int client_seq, unsigned int idx_seq, - void *context) +static int update_messageset(struct messageset_context *ctx, + struct index_mailbox *ibox, enum mail_flags flags, + enum modify_type modify_type, int notify) { - struct update_context *ctx = context; struct mail_storage *storage; - enum mail_flags flags; + const struct messageset_mail *mail; const char **custom_flags; + enum mail_flags new_flags; + + storage = ibox->box.storage; + custom_flags = mail_custom_flags_list_get(ibox->index->custom_flags); - switch (ctx->modify_type) { - case MODIFY_ADD: - flags = rec->msg_flags | ctx->flags; - break; - case MODIFY_REMOVE: - flags = rec->msg_flags & ~ctx->flags; - break; - case MODIFY_REPLACE: - flags = ctx->flags; - break; - default: - i_unreached(); + while ((mail = index_messageset_next(ctx)) != NULL) { + switch (modify_type) { + case MODIFY_ADD: + new_flags = mail->rec->msg_flags | flags; + break; + case MODIFY_REMOVE: + new_flags = mail->rec->msg_flags & ~flags; + break; + case MODIFY_REPLACE: + new_flags = flags; + break; + default: + i_unreached(); + } + + if (!ibox->index->update_flags(ibox->index, mail->rec, + mail->idx_seq, new_flags, FALSE)) + return -1; + + if (mail_custom_flags_has_changes(ibox->index->custom_flags)) { + storage->callbacks->new_custom_flags(&ibox->box, + custom_flags, MAIL_CUSTOM_FLAGS_COUNT, + storage->callback_context); + } + + if (notify) { + if (mail->rec->uid >= ibox->index->first_recent_uid) + new_flags |= MAIL_RECENT; + + storage->callbacks->update_flags(&ibox->box, + mail->client_seq, mail->rec->uid, new_flags, + custom_flags, MAIL_CUSTOM_FLAGS_COUNT, + storage->callback_context); + } } - if (!index->update_flags(index, rec, idx_seq, flags, FALSE)) - return FALSE; - - storage = ctx->ibox->box.storage; - if (mail_custom_flags_has_changes(index->custom_flags)) { - storage->callbacks->new_custom_flags(&ctx->ibox->box, - mail_custom_flags_list_get(index->custom_flags), - MAIL_CUSTOM_FLAGS_COUNT, storage->callback_context); - } - - if (ctx->notify) { - if (rec->uid >= index->first_recent_uid) - flags |= MAIL_RECENT; - - custom_flags = mail_custom_flags_list_get(index->custom_flags); - storage->callbacks->update_flags(&ctx->ibox->box, - client_seq, rec->uid, - flags, custom_flags, - MAIL_CUSTOM_FLAGS_COUNT, - storage->callback_context); - } - - return TRUE; + return 1; } -int index_storage_update_flags(struct mailbox *box, - const char *messageset, int uidset, - enum mail_flags flags, - const char *custom_flags[], +int index_storage_update_flags(struct mailbox *box, const char *messageset, + int uidset, const struct mail_full_flags *flags, enum modify_type modify_type, int notify, int *all_found) { struct index_mailbox *ibox = (struct index_mailbox *) box; - struct update_context ctx; - int ret; + struct messageset_context *ctx; + enum mail_flags mail_flags; + int ret, ret2; if (box->readonly) { mail_storage_set_error(box->storage, "Mailbox is read-only"); return FALSE; } - if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags)) + mail_flags = flags->flags; + if (!index_mailbox_fix_custom_flags(ibox, &mail_flags, + flags->custom_flags, + flags->custom_flags_count)) return FALSE; if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE)) @@ -85,18 +83,16 @@ if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_UNLOCK)) return FALSE; - ctx.ibox = ibox; - ctx.flags = flags & ~MAIL_RECENT; /* \Recent can't be changed */ - ctx.modify_type = modify_type; - ctx.notify = notify; + mail_flags &= ~MAIL_RECENT; /* \Recent can't be changed */ - ret = index_messageset_foreach(ibox, messageset, uidset, - update_cb, &ctx); + ctx = index_messageset_init(ibox, messageset, uidset); + ret = update_messageset(ctx, ibox, mail_flags, modify_type, notify); + ret2 = index_messageset_deinit(ctx); if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK)) return FALSE; if (all_found != NULL) - *all_found = ret == 1; - return ret >= 0; + *all_found = ret2 > 0; + return ret >= 0 && ret2 >= 0; } diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/maildir/maildir-copy.c --- a/src/lib-storage/index/maildir/maildir-copy.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-copy.c Mon Jan 20 16:52:51 2003 +0200 @@ -1,87 +1,86 @@ /* Copyright (C) 2002 Timo Sirainen */ #include "lib.h" -#include "index-messageset.h" #include "maildir-index.h" #include "maildir-storage.h" #include "mail-custom-flags.h" +#include "mail-index-util.h" +#include "index-messageset.h" #include #include -struct copy_hard_context { - struct mail_storage *storage; - struct index_mailbox *dest; - int error; - const char **custom_flags; -}; - -static int copy_hard_cb(struct mail_index *index, - struct mail_index_record *rec, - unsigned int client_seq __attr_unused__, - unsigned int idx_seq __attr_unused__, void *context) +static int hardlink_messageset(struct messageset_context *ctx, + struct index_mailbox *src, + struct index_mailbox *dest) { - struct copy_hard_context *ctx = context; + struct mail_index *index = src->index; + const struct messageset_mail *mail; enum mail_flags flags; - const char *fname; - char src[PATH_MAX], dest[PATH_MAX]; + const char **custom_flags; + const char *fname, *src_fname, *dest_fname; + + custom_flags = mail_custom_flags_list_get(index->custom_flags); - flags = rec->msg_flags; - if (!index_mailbox_fix_custom_flags(ctx->dest, &flags, - ctx->custom_flags)) - return FALSE; + while ((mail = index_messageset_next(ctx)) != NULL) { + flags = mail->rec->msg_flags; + if (!index_mailbox_fix_custom_flags(dest, &flags, + custom_flags, + MAIL_CUSTOM_FLAGS_COUNT)) + return -1; - /* link the file */ - fname = index->lookup_field(index, rec, DATA_FIELD_LOCATION); - if (str_ppath(src, sizeof(src), - index->mailbox_path, "cur/", fname) < 0) { - mail_storage_set_critical(ctx->storage, "Filename too long: %s", - fname); - return FALSE; + /* link the file */ + fname = index->lookup_field(index, mail->rec, + DATA_FIELD_LOCATION); + if (fname == NULL) { + index_set_corrupted(index, + "Missing location field for record %u", + mail->rec->uid); + return -1; + } + + t_push(); + src_fname = t_strconcat(index->mailbox_path, "cur/", + fname, NULL); + dest_fname = t_strconcat(dest->index->mailbox_path, "new/", + maildir_filename_set_flags( + maildir_generate_tmp_filename(), flags), NULL); + + if (link(src_fname, dest_fname) < 0) { + if (errno != EXDEV) { + mail_storage_set_critical(src->box.storage, + "link(%s, %s) failed: %m", + src_fname, dest_fname); + t_pop(); + return -1; + } + t_pop(); + return 0; + } + t_pop(); } - fname = maildir_filename_set_flags(maildir_generate_tmp_filename(), - flags); - if (str_ppath(dest, sizeof(dest), - ctx->dest->index->mailbox_path, "new/", fname) < 0) { - mail_storage_set_critical(ctx->storage, "Filename too long: %s", - fname); - return FALSE; - } - - if (link(src, dest) == 0) - return TRUE; - else { - if (errno != EXDEV) { - mail_storage_set_critical(ctx->storage, "link(%s, %s) " - "failed: %m", src, dest); - ctx->error = TRUE; - } - return FALSE; - } + return 1; } -static int maildir_copy_with_hardlinks(struct index_mailbox *src, - struct index_mailbox *dest, - const char *messageset, int uidset) +static int copy_with_hardlinks(struct index_mailbox *src, + struct index_mailbox *dest, + const char *messageset, int uidset) { - struct copy_hard_context ctx; + struct messageset_context *ctx; int ret; if (!index_storage_sync_and_lock(src, TRUE, MAIL_LOCK_SHARED)) return -1; - ctx.storage = src->box.storage; - ctx.dest = dest; - ctx.error = FALSE; - ctx.custom_flags = mail_custom_flags_list_get(src->index->custom_flags); - - ret = index_messageset_foreach(src, messageset, uidset, - copy_hard_cb, &ctx); + ctx = index_messageset_init(src, messageset, uidset); + ret = hardlink_messageset(ctx, src, dest); + if (index_messageset_deinit(ctx) < 0) + ret = -1; (void)index_storage_lock(src, MAIL_LOCK_UNLOCK); - return ctx.error ? -1 : ret; + return ret; } int maildir_storage_copy(struct mailbox *box, struct mailbox *destbox, @@ -99,7 +98,7 @@ destbox->storage == box->storage) { /* both source and destination mailbox are in maildirs and copy_with_hardlinks option is on, do it */ - switch (maildir_copy_with_hardlinks(ibox, + switch (copy_with_hardlinks(ibox, (struct index_mailbox *) destbox, messageset, uidset)) { case 1: return TRUE; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/maildir/maildir-save.c --- a/src/lib-storage/index/maildir/maildir-save.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-save.c Mon Jan 20 16:52:51 2003 +0200 @@ -78,12 +78,14 @@ return fname; } -int maildir_storage_save(struct mailbox *box, enum mail_flags flags, - const char *custom_flags[], time_t internal_date, +int maildir_storage_save(struct mailbox *box, + const struct mail_full_flags *flags, + time_t internal_date, int timezone_offset __attr_unused__, struct istream *data, uoff_t data_size) { struct index_mailbox *ibox = (struct index_mailbox *) box; + enum mail_flags mail_flags; struct utimbuf buf; const char *tmpdir, *fname, *tmp_path, *new_path; int failed; @@ -93,7 +95,10 @@ return FALSE; } - if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags)) + mail_flags = flags->flags; + if (!index_mailbox_fix_custom_flags(ibox, &mail_flags, + flags->custom_flags, + flags->custom_flags_count)) return FALSE; t_push(); @@ -107,7 +112,7 @@ } tmp_path = t_strconcat(tmpdir, "/", fname, NULL); - fname = maildir_filename_set_flags(fname, flags); + fname = maildir_filename_set_flags(fname, mail_flags); new_path = t_strconcat(ibox->index->mailbox_path, "/new/", fname, NULL); /* set the internal_date by modifying mtime */ diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/maildir/maildir-storage.c --- a/src/lib-storage/index/maildir/maildir-storage.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.c Mon Jan 20 16:52:51 2003 +0200 @@ -560,8 +560,15 @@ index_storage_expunge, index_storage_update_flags, maildir_storage_copy, - index_storage_fetch, - index_storage_search, + index_storage_fetch_init, + index_storage_fetch_deinit, + index_storage_fetch_next, + index_storage_fetch_uid, + index_storage_fetch_seq, + index_storage_search_get_sorting, + index_storage_search_init, + index_storage_search_deinit, + index_storage_search_next, maildir_storage_save, mail_storage_is_inconsistency_error, diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/maildir/maildir-storage.h --- a/src/lib-storage/index/maildir/maildir-storage.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.h Mon Jan 20 16:52:51 2003 +0200 @@ -5,8 +5,8 @@ int maildir_storage_copy(struct mailbox *box, struct mailbox *destbox, const char *messageset, int uidset); -int maildir_storage_save(struct mailbox *box, enum mail_flags flags, - const char *custom_flags[], +int maildir_storage_save(struct mailbox *box, + const struct mail_full_flags *flags, time_t internal_date, int timezone_offset, struct istream *data, uoff_t data_size); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/mbox/mbox-save.c --- a/src/lib-storage/index/mbox/mbox-save.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-save.c Mon Jan 20 16:52:51 2003 +0200 @@ -107,11 +107,12 @@ static int write_flags(struct mail_storage *storage, struct ostream *output, const char *mbox_path, - enum mail_flags flags, const char *custom_flags[]) + const struct mail_full_flags *full_flags) { + enum mail_flags flags = full_flags->flags; const char *str; unsigned int field; - int i; + unsigned int i; if (flags == 0) return TRUE; @@ -138,15 +139,18 @@ return write_error(storage, mbox_path); field = 1 << MAIL_CUSTOM_FLAG_1_BIT; - for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++, field <<= 1) { - if ((flags & field) && custom_flags[i] != NULL) { + for (i = 0; i < full_flags->custom_flags_count; i++) { + const char *custom_flag = full_flags->custom_flags[i]; + + if ((flags & field) && custom_flag != NULL) { if (o_stream_send(output, " ", 1) < 0) return write_error(storage, mbox_path); - if (o_stream_send_str(output, - custom_flags[i]) < 0) + if (o_stream_send_str(output, custom_flag) < 0) return write_error(storage, mbox_path); } + + field <<= 1; } if (o_stream_send(output, "\n", 1) < 0) @@ -156,8 +160,8 @@ return TRUE; } -int mbox_storage_save(struct mailbox *box, enum mail_flags flags, - const char *custom_flags[], time_t internal_date, +int mbox_storage_save(struct mailbox *box, const struct mail_full_flags *flags, + time_t internal_date, int timezone_offset __attr_unused__, struct istream *data, uoff_t data_size) { @@ -176,8 +180,10 @@ /* we don't need the real flag positions, easier to keep using our own. they need to be checked/added though. */ - real_flags = flags; - if (!index_mailbox_fix_custom_flags(ibox, &real_flags, custom_flags)) + real_flags = flags->flags; + if (!index_mailbox_fix_custom_flags(ibox, &real_flags, + flags->custom_flags, + flags->custom_flags_count)) return FALSE; if (!index_storage_sync_and_lock(ibox, FALSE, MAIL_LOCK_EXCLUSIVE)) @@ -198,8 +204,7 @@ if (!write_from_line(box->storage, output, mbox_path, internal_date) || - !write_flags(box->storage, output, mbox_path, flags, - custom_flags) || + !write_flags(box->storage, output, mbox_path, flags) || !index_storage_save(box->storage, mbox_path, data, output, data_size) || !mbox_append_lf(box->storage, output, mbox_path)) { diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/mbox/mbox-storage.c --- a/src/lib-storage/index/mbox/mbox-storage.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-storage.c Mon Jan 20 16:52:51 2003 +0200 @@ -620,8 +620,15 @@ index_storage_expunge, index_storage_update_flags, index_storage_copy, - index_storage_fetch, - index_storage_search, + index_storage_fetch_init, + index_storage_fetch_deinit, + index_storage_fetch_next, + index_storage_fetch_uid, + index_storage_fetch_seq, + index_storage_search_get_sorting, + index_storage_search_init, + index_storage_search_deinit, + index_storage_search_next, mbox_storage_save, mail_storage_is_inconsistency_error, diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/index/mbox/mbox-storage.h --- a/src/lib-storage/index/mbox/mbox-storage.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-storage.h Mon Jan 20 16:52:51 2003 +0200 @@ -5,9 +5,8 @@ int mbox_storage_copy(struct mailbox *box, struct mailbox *destbox, const char *messageset, int uidset); -int mbox_storage_save(struct mailbox *box, enum mail_flags flags, - const char *custom_flags[], time_t internal_date, - int timezone_offset, +int mbox_storage_save(struct mailbox *box, const struct mail_full_flags *flags, + time_t internal_date, int timezone_offset, struct istream *data, uoff_t data_size); int mbox_find_mailboxes(struct mail_storage *storage, const char *mask, diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/mail-search.c --- a/src/lib-storage/mail-search.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/mail-search.c Mon Jan 20 16:52:51 2003 +0200 @@ -3,377 +3,6 @@ #include "lib.h" #include "mail-search.h" -struct search_build_data { - pool_t pool; - const char *error; -}; - -static struct mail_search_arg * -search_arg_new(pool_t pool, enum mail_search_arg_type type) -{ - struct mail_search_arg *arg; - - arg = p_new(pool, struct mail_search_arg, 1); - arg->type = type; - - return arg; -} - -#define ARG_NEW(type, value) \ - arg_new(data, args, next_sarg, type, value) - -static int arg_new(struct search_build_data *data, struct imap_arg **args, - struct mail_search_arg **next_sarg, - enum mail_search_arg_type type, int value) -{ - struct mail_search_arg *sarg; - - *next_sarg = sarg = search_arg_new(data->pool, type); - if (value == 0) - return TRUE; - - /* first arg */ - if ((*args)->type == IMAP_ARG_EOL) { - data->error = "Missing parameter for argument"; - return FALSE; - } - - if ((*args)->type != IMAP_ARG_ATOM && - (*args)->type != IMAP_ARG_STRING) { - data->error = "Invalid parameter for argument"; - return FALSE; - } - - sarg->value.str = str_ucase(IMAP_ARG_STR(*args)); - *args += 1; - - /* second arg */ - if (value == 2) { - if ((*args)->type == IMAP_ARG_EOL) { - data->error = "Missing parameter for argument"; - return FALSE; - } - - if ((*args)->type != IMAP_ARG_ATOM && - (*args)->type != IMAP_ARG_STRING) { - data->error = "Invalid parameter for argument"; - return FALSE; - } - - sarg->hdr_field_name = sarg->value.str; - sarg->value.str = str_ucase(IMAP_ARG_STR(*args)); - *args += 1; - } - - return TRUE; -} - -static int search_arg_build(struct search_build_data *data, - struct imap_arg **args, - struct mail_search_arg **next_sarg) -{ - struct mail_search_arg **subargs; - struct imap_arg *arg; - char *str; - - if ((*args)->type == IMAP_ARG_EOL) { - data->error = "Missing argument"; - return FALSE; - } - - arg = *args; - - if (arg->type == IMAP_ARG_NIL) { - /* NIL not allowed */ - data->error = "NIL not allowed"; - return FALSE; - } - - if (arg->type == IMAP_ARG_LIST) { - struct imap_arg *listargs = IMAP_ARG_LIST(arg)->args; - - *next_sarg = search_arg_new(data->pool, SEARCH_SUB); - subargs = &(*next_sarg)->value.subargs; - while (listargs->type != IMAP_ARG_EOL) { - if (!search_arg_build(data, &listargs, subargs)) - return FALSE; - subargs = &(*subargs)->next; - } - - *args += 1; - return TRUE; - } - - i_assert(arg->type == IMAP_ARG_ATOM || - arg->type == IMAP_ARG_STRING); - - /* string argument - get the name and jump to next */ - str = IMAP_ARG_STR(arg); - *args += 1; - str_ucase(str); - - switch (*str) { - case 'A': - if (strcmp(str, "ANSWERED") == 0) - return ARG_NEW(SEARCH_ANSWERED, 0); - else if (strcmp(str, "ALL") == 0) - return ARG_NEW(SEARCH_ALL, 0); - break; - case 'B': - if (strcmp(str, "BODY") == 0) { - /* */ - return ARG_NEW(SEARCH_BODY, 1); - } else if (strcmp(str, "BEFORE") == 0) { - /* */ - return ARG_NEW(SEARCH_BEFORE, 1); - } else if (strcmp(str, "BCC") == 0) { - /* */ - return ARG_NEW(SEARCH_BCC, 1); - } - break; - case 'C': - if (strcmp(str, "CC") == 0) { - /* */ - return ARG_NEW(SEARCH_CC, 1); - } - break; - case 'D': - if (strcmp(str, "DELETED") == 0) - return ARG_NEW(SEARCH_DELETED, 0); - else if (strcmp(str, "DRAFT") == 0) - return ARG_NEW(SEARCH_DRAFT, 0); - break; - case 'F': - if (strcmp(str, "FLAGGED") == 0) - return ARG_NEW(SEARCH_FLAGGED, 0); - else if (strcmp(str, "FROM") == 0) { - /* */ - return ARG_NEW(SEARCH_FROM, 1); - } - break; - case 'H': - if (strcmp(str, "HEADER") == 0) { - /* */ - const char *key; - - if ((*args)->type == IMAP_ARG_EOL) { - data->error = "Missing parameter for HEADER"; - return FALSE; - } - if ((*args)->type != IMAP_ARG_ATOM && - (*args)->type != IMAP_ARG_STRING) { - data->error = "Invalid parameter for HEADER"; - return FALSE; - } - - key = str_ucase(IMAP_ARG_STR(*args)); - - if (strcmp(key, "FROM") == 0) { - *args += 1; - return ARG_NEW(SEARCH_FROM, 1); - } else if (strcmp(key, "TO") == 0) { - *args += 1; - return ARG_NEW(SEARCH_TO, 1); - } else if (strcmp(key, "CC") == 0) { - *args += 1; - return ARG_NEW(SEARCH_CC, 1); - } else if (strcmp(key, "BCC") == 0) { - *args += 1; - return ARG_NEW(SEARCH_BCC, 1); - } else if (strcmp(key, "SUBJECT") == 0) { - *args += 1; - return ARG_NEW(SEARCH_SUBJECT, 1); - } else if (strcmp(key, "IN-REPLY-TO") == 0) { - *args += 1; - return ARG_NEW(SEARCH_IN_REPLY_TO, 1); - } else if (strcmp(key, "MESSAGE-ID") == 0) { - *args += 1; - return ARG_NEW(SEARCH_MESSAGE_ID, 1); - } else { - return ARG_NEW(SEARCH_HEADER, 2); - } - } - break; - case 'K': - if (strcmp(str, "KEYWORD") == 0) { - /* */ - return ARG_NEW(SEARCH_KEYWORD, 1); - } - break; - case 'L': - if (strcmp(str, "LARGER") == 0) { - /* */ - return ARG_NEW(SEARCH_LARGER, 1); - } - break; - case 'N': - if (strcmp(str, "NOT") == 0) { - if (!search_arg_build(data, args, next_sarg)) - return FALSE; - (*next_sarg)->not = !(*next_sarg)->not; - return TRUE; - } else if (strcmp(str, "NEW") == 0) { - /* NEW == (RECENT UNSEEN) */ - *next_sarg = search_arg_new(data->pool, SEARCH_SUB); - - subargs = &(*next_sarg)->value.subargs; - *subargs = search_arg_new(data->pool, SEARCH_RECENT); - (*subargs)->next = search_arg_new(data->pool, - SEARCH_SEEN); - (*subargs)->next->not = TRUE; - return TRUE; - } - break; - case 'O': - if (strcmp(str, "OR") == 0) { - /* */ - *next_sarg = search_arg_new(data->pool, SEARCH_OR); - - subargs = &(*next_sarg)->value.subargs; - for (;;) { - if (!search_arg_build(data, args, subargs)) - return FALSE; - - subargs = &(*subargs)->next; - - /* OR OR ... - put them all - under one SEARCH_OR list. */ - if ((*args)->type == IMAP_ARG_EOL) - break; - - if ((*args)->type != IMAP_ARG_ATOM || - strcasecmp(IMAP_ARG_STR(*args), "OR") != 0) - break; - - *args += 1; - } - - if (!search_arg_build(data, args, subargs)) - return FALSE; - return TRUE; - } if (strcmp(str, "ON") == 0) { - /* */ - return ARG_NEW(SEARCH_ON, 1); - } if (strcmp(str, "OLD") == 0) { - /* OLD == NOT RECENT */ - if (!ARG_NEW(SEARCH_RECENT, 0)) - return FALSE; - - (*next_sarg)->not = TRUE; - return TRUE; - } - break; - case 'R': - if (strcmp(str, "RECENT") == 0) - return ARG_NEW(SEARCH_RECENT, 0); - break; - case 'S': - if (strcmp(str, "SEEN") == 0) - return ARG_NEW(SEARCH_SEEN, 0); - else if (strcmp(str, "SUBJECT") == 0) { - /* */ - return ARG_NEW(SEARCH_SUBJECT, 1); - } else if (strcmp(str, "SENTBEFORE") == 0) { - /* */ - return ARG_NEW(SEARCH_SENTBEFORE, 1); - } else if (strcmp(str, "SENTON") == 0) { - /* */ - return ARG_NEW(SEARCH_SENTON, 1); - } else if (strcmp(str, "SENTSINCE") == 0) { - /* */ - return ARG_NEW(SEARCH_SENTSINCE, 1); - } else if (strcmp(str, "SINCE") == 0) { - /* */ - return ARG_NEW(SEARCH_SINCE, 1); - } else if (strcmp(str, "SMALLER") == 0) { - /* */ - return ARG_NEW(SEARCH_SMALLER, 1); - } - break; - case 'T': - if (strcmp(str, "TEXT") == 0) { - /* */ - return ARG_NEW(SEARCH_TEXT, 1); - } else if (strcmp(str, "TO") == 0) { - /* */ - return ARG_NEW(SEARCH_TO, 1); - } - break; - case 'U': - if (strcmp(str, "UID") == 0) { - /* */ - return ARG_NEW(SEARCH_UID, 1); - } else if (strcmp(str, "UNANSWERED") == 0) { - if (!ARG_NEW(SEARCH_ANSWERED, 0)) - return FALSE; - (*next_sarg)->not = TRUE; - return TRUE; - } else if (strcmp(str, "UNDELETED") == 0) { - if (!ARG_NEW(SEARCH_DELETED, 0)) - return FALSE; - (*next_sarg)->not = TRUE; - return TRUE; - } else if (strcmp(str, "UNDRAFT") == 0) { - if (!ARG_NEW(SEARCH_DRAFT, 0)) - return FALSE; - (*next_sarg)->not = TRUE; - return TRUE; - } else if (strcmp(str, "UNFLAGGED") == 0) { - if (!ARG_NEW(SEARCH_FLAGGED, 0)) - return FALSE; - (*next_sarg)->not = TRUE; - return TRUE; - } else if (strcmp(str, "UNKEYWORD") == 0) { - if (!ARG_NEW(SEARCH_KEYWORD, 0)) - return FALSE; - (*next_sarg)->not = TRUE; - return TRUE; - } else if (strcmp(str, "UNSEEN") == 0) { - if (!ARG_NEW(SEARCH_SEEN, 0)) - return FALSE; - (*next_sarg)->not = TRUE; - return TRUE; - } - break; - default: - if (*str == '*' || (*str >= '0' && *str <= '9')) { - /* */ - if (!ARG_NEW(SEARCH_SET, 0)) - return FALSE; - - (*next_sarg)->value.str = str; - return TRUE; - } - break; - } - - data->error = t_strconcat("Unknown argument ", str, NULL); - return FALSE; -} - -struct mail_search_arg * -mail_search_args_build(pool_t pool, struct imap_arg *args, const char **error) -{ - struct search_build_data data; - struct mail_search_arg *first_sarg, **sargs; - - data.pool = pool; - data.error = NULL; - - /* get the first arg */ - first_sarg = NULL; sargs = &first_sarg; - while (args->type != IMAP_ARG_EOL) { - if (!search_arg_build(&data, &args, sargs)) { - *error = data.error; - return NULL; - } - sargs = &(*sargs)->next; - } - - *error = NULL; - return first_sarg; -} - void mail_search_args_reset(struct mail_search_arg *args) { while (args != NULL) { diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/mail-search.h --- a/src/lib-storage/mail-search.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/mail-search.h Mon Jan 20 16:52:51 2003 +0200 @@ -1,9 +1,6 @@ #ifndef __MAIL_SEARCH_H #define __MAIL_SEARCH_H -#include "imap-parser.h" -#include "mail-storage.h" - enum mail_search_arg_type { SEARCH_OR, SEARCH_SUB, @@ -70,10 +67,6 @@ typedef void (*mail_search_foreach_callback_t)(struct mail_search_arg *arg, void *context); -/* Builds search arguments based on IMAP arguments. */ -struct mail_search_arg * -mail_search_args_build(pool_t pool, struct imap_arg *args, const char **error); - /* Reset the results in search arguments */ void mail_search_args_reset(struct mail_search_arg *args); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/mail-sort.c --- a/src/lib-storage/mail-sort.c Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,588 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -/* Implementation of draft-ietf-imapext-sort-10 sorting algorithm. - Pretty messy code actually, adding any sort types requires care. - This is pretty fast however and takes only as much memory as needed to be - reasonably fast. */ - -#include "lib.h" -#include "buffer.h" -#include "hash.h" -#include "ostream.h" -#include "imap-base-subject.h" -#include "mail-sort.h" - -#include - -#define IS_SORT_STRING(type) \ - ((type) == MAIL_SORT_CC || (type) == MAIL_SORT_FROM || \ - (type) == MAIL_SORT_SUBJECT || (type) == MAIL_SORT_TO) - -#define IS_SORT_TIME(type) \ - ((type) == MAIL_SORT_ARRIVAL || (type) == MAIL_SORT_DATE) - -struct mail_sort_context { - enum mail_sort_type output[MAX_SORT_PROGRAM_SIZE]; - enum mail_sort_type common_mask, cache_mask; - - struct ostream *outstream; - const struct mail_sort_callbacks *callbacks; - void *func_context; - - buffer_t *sort_buffer; - size_t sort_element_size; - - pool_t temp_pool, str_pool; - struct hash_table *string_table; - - time_t last_arrival, last_date; - uoff_t last_size; - char *last_cc, *last_from, *last_subject, *last_to; -}; - -static void mail_sort_flush(struct mail_sort_context *ctx); - -static enum mail_sort_type -mail_sort_normalize(const enum mail_sort_type *input, buffer_t *output) -{ - enum mail_sort_type type, mask = 0; - int pos, reverse; - - reverse = FALSE; - for (pos = 0; *input != MAIL_SORT_END; input++) { - if (*input == MAIL_SORT_REVERSE) - reverse = !reverse; - else { - if ((mask & *input) == 0) { - if (reverse) { - type = MAIL_SORT_REVERSE; - buffer_append(output, - &type, sizeof(type)); - } - - buffer_append(output, input, sizeof(*input)); - mask |= *input; - } - - reverse = FALSE; - } - } - - type = MAIL_SORT_END; - buffer_append(output, &type, sizeof(type)); - - return mask; -} - -static enum mail_sort_type -mail_sort_get_common_mask(const enum mail_sort_type *input, - enum mail_sort_type **output) -{ - enum mail_sort_type mask = 0; - - while (*input == **output && *input != MAIL_SORT_END) { - if (*input != MAIL_SORT_REVERSE) - mask |= *input; - input++; (*output)++; - } - - return mask; -} - -struct mail_sort_context * -mail_sort_init(const enum mail_sort_type *input, enum mail_sort_type *output, - struct ostream *outstream, - const struct mail_sort_callbacks *callbacks, void *context) -{ - /* @UNSAFE */ - struct mail_sort_context *ctx; - enum mail_sort_type norm_input[MAX_SORT_PROGRAM_SIZE]; - enum mail_sort_type norm_output[MAX_SORT_PROGRAM_SIZE]; - buffer_t *buf; - int i; - - ctx = i_new(struct mail_sort_context, 1); - ctx->temp_pool = pool_alloconly_create("sort temp", 8192); - ctx->outstream = outstream; - - t_push(); - buf = buffer_create_data(data_stack_pool, - norm_input, sizeof(norm_input)); - mail_sort_normalize(input, buf); - - buf = buffer_create_data(data_stack_pool, - norm_output, sizeof(norm_output)); - mail_sort_normalize(output, buf); - t_pop(); - - /* remove the common part from output, we already know input is sorted - that much so we don't have to worry about it. */ - output = norm_output; - ctx->common_mask = mail_sort_get_common_mask(norm_input, &output); - - for (i = 0; output[i] != MAIL_SORT_END; i++) - ctx->output[i] = output[i]; - ctx->output[i] = MAIL_SORT_END; - - /* figure out what data we'd like to cache */ - ctx->sort_element_size = sizeof(unsigned int); - ctx->cache_mask = 0; - - for (i = 0; output[i] != MAIL_SORT_END; i++) { - if (IS_SORT_STRING(output[i])) { - ctx->sort_element_size += sizeof(const char *); - - /* cache the second rule as well, if available */ - if (ctx->cache_mask != 0) { - ctx->cache_mask |= output[i]; - break; - } - ctx->cache_mask |= output[i]; - } else if (IS_SORT_TIME(output[i])) { - ctx->sort_element_size += sizeof(time_t); - ctx->cache_mask |= output[i]; - break; - } else if (output[i] == MAIL_SORT_SIZE) { - ctx->sort_element_size += sizeof(uoff_t); - ctx->cache_mask |= output[i]; - break; - } - } - - if ((ctx->cache_mask & MAIL_SORT_CC) || - (ctx->cache_mask & MAIL_SORT_FROM) || - (ctx->cache_mask & MAIL_SORT_TO) || - (ctx->cache_mask & MAIL_SORT_SUBJECT)) { - ctx->str_pool = pool_alloconly_create("sort str", 8192); - ctx->string_table = hash_create(default_pool, ctx->str_pool, - 0, str_hash, - (hash_cmp_callback_t)strcmp); - } - - ctx->sort_buffer = buffer_create_dynamic(system_pool, - 128 * ctx->sort_element_size, - (size_t)-1); - - ctx->callbacks = callbacks; - ctx->func_context = context; - return ctx; -} - -void mail_sort_deinit(struct mail_sort_context *ctx) -{ - mail_sort_flush(ctx); - - if (ctx->string_table != NULL) - hash_destroy(ctx->string_table); - if (ctx->str_pool != NULL) - pool_unref(ctx->str_pool); - buffer_free(ctx->sort_buffer); - pool_unref(ctx->temp_pool); - - i_free(ctx->last_cc); - i_free(ctx->last_from); - i_free(ctx->last_subject); - i_free(ctx->last_to); - - i_free(ctx); -} - -static const char *string_table_get(struct mail_sort_context *ctx, - const char *str) -{ - char *value; - - if (str == NULL) - return NULL; - if (*str == '\0') - return ""; - - value = hash_lookup(ctx->string_table, str); - if (value == NULL) { - value = p_strdup(ctx->str_pool, str); - hash_insert(ctx->string_table, value, value); - } - - return value; -} - -static void mail_sort_check_flush(struct mail_sort_context *ctx, - unsigned int id) -{ - const char *str; - time_t t; - uoff_t size; - int changed = FALSE; - - if (ctx->common_mask & MAIL_SORT_ARRIVAL) { - t = ctx->callbacks->input_time(MAIL_SORT_ARRIVAL, id, - ctx->func_context); - if (t != ctx->last_arrival) { - ctx->last_arrival = t; - changed = TRUE; - } - } - - if (ctx->common_mask & MAIL_SORT_CC) { - str = ctx->callbacks->input_mailbox(MAIL_SORT_CC, id, - ctx->func_context); - str = str_ucase(t_strdup_noconst(str)); - if (strcmp(str, ctx->last_cc) != 0) { - i_free(ctx->last_cc); - ctx->last_cc = i_strdup(str); - changed = TRUE; - } - } - - if (ctx->common_mask & MAIL_SORT_DATE) { - t = ctx->callbacks->input_time(MAIL_SORT_DATE, id, - ctx->func_context); - if (t != ctx->last_date) { - ctx->last_date = t; - changed = TRUE; - } - } - - if (ctx->common_mask & MAIL_SORT_FROM) { - str = ctx->callbacks->input_mailbox(MAIL_SORT_FROM, id, - ctx->func_context); - str = str_ucase(t_strdup_noconst(str)); - if (strcmp(str, ctx->last_from) != 0) { - i_free(ctx->last_from); - ctx->last_from = i_strdup(str); - changed = TRUE; - } - } - - if (ctx->common_mask & MAIL_SORT_SIZE) { - size = ctx->callbacks->input_uofft(MAIL_SORT_SIZE, id, - ctx->func_context); - if (size != ctx->last_size) { - ctx->last_size = size; - changed = TRUE; - } - } - - if (ctx->common_mask & MAIL_SORT_SUBJECT) { - str = ctx->callbacks->input_str(MAIL_SORT_SUBJECT, id, - ctx->func_context); - p_clear(ctx->temp_pool); - str = imap_get_base_subject_cased(ctx->temp_pool, str, NULL); - - if (strcmp(str, ctx->last_subject) != 0) { - i_free(ctx->last_subject); - ctx->last_subject = i_strdup(str); - changed = TRUE; - } - } - - if (ctx->common_mask & MAIL_SORT_TO) { - str = ctx->callbacks->input_mailbox(MAIL_SORT_TO, id, - ctx->func_context); - str = str_ucase(t_strdup_noconst(str)); - if (strcmp(str, ctx->last_to) != 0) { - i_free(ctx->last_to); - ctx->last_to = i_strdup(str); - changed = TRUE; - } - } - - if (changed) - mail_sort_flush(ctx); -} - -void mail_sort_input(struct mail_sort_context *ctx, unsigned int id) -{ - /* @UNSAFE */ - unsigned char *buf; - time_t t; - uoff_t size; - const char *str; - size_t pos; - - t_push(); - if (ctx->common_mask != 0) - mail_sort_check_flush(ctx, id); - - buf = buffer_append_space(ctx->sort_buffer, ctx->sort_element_size); - memcpy(buf, &id, sizeof(id)); pos = sizeof(id); - - if (ctx->cache_mask & MAIL_SORT_ARRIVAL) { - if (ctx->common_mask & MAIL_SORT_ARRIVAL) - t = ctx->last_arrival; - else { - t = ctx->callbacks->input_time(MAIL_SORT_ARRIVAL, id, - ctx->func_context); - } - memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t); - } - - if (ctx->cache_mask & MAIL_SORT_DATE) { - if (ctx->common_mask & MAIL_SORT_DATE) - t = ctx->last_date; - else { - t = ctx->callbacks->input_time(MAIL_SORT_DATE, id, - ctx->func_context); - } - memcpy(buf + pos, &t, sizeof(t)); pos += sizeof(t); - } - - if (ctx->cache_mask & MAIL_SORT_SIZE) { - if (ctx->common_mask & MAIL_SORT_SIZE) - size = ctx->last_size; - else { - size = ctx->callbacks->input_uofft(MAIL_SORT_SIZE, id, - ctx->func_context); - } - - memcpy(buf + pos, &size, sizeof(size)); pos += sizeof(size); - } - - if (ctx->cache_mask & MAIL_SORT_CC) { - if (ctx->common_mask & MAIL_SORT_CC) - str = ctx->last_cc; - else { - str = ctx->callbacks->input_mailbox(MAIL_SORT_CC, id, - ctx->func_context); - if (str != NULL) - str = str_ucase(t_strdup_noconst(str)); - } - str = string_table_get(ctx, str); - - memcpy(buf + pos, &str, sizeof(const char *)); - pos += sizeof(const char *); - } - - if (ctx->cache_mask & MAIL_SORT_FROM) { - if (ctx->common_mask & MAIL_SORT_FROM) - str = ctx->last_from; - else { - str = ctx->callbacks->input_mailbox(MAIL_SORT_FROM, id, - ctx->func_context); - if (str != NULL) - str = str_ucase(t_strdup_noconst(str)); - } - str = string_table_get(ctx, str); - - memcpy(buf + pos, &str, sizeof(const char *)); - pos += sizeof(const char *); - } - - if (ctx->cache_mask & MAIL_SORT_TO) { - if (ctx->common_mask & MAIL_SORT_TO) - str = ctx->last_to; - else { - str = ctx->callbacks->input_mailbox(MAIL_SORT_TO, id, - ctx->func_context); - if (str != NULL) - str = str_ucase(t_strdup_noconst(str)); - } - str = string_table_get(ctx, str); - - memcpy(buf + pos, &str, sizeof(const char *)); - pos += sizeof(const char *); - } - - if (ctx->cache_mask & MAIL_SORT_SUBJECT) { - if (ctx->common_mask & MAIL_SORT_SUBJECT) - str = ctx->last_subject; - else { - str = ctx->callbacks->input_str(MAIL_SORT_SUBJECT, id, - ctx->func_context); - p_clear(ctx->temp_pool); - str = imap_get_base_subject_cased(ctx->temp_pool, - str, NULL); - } - str = string_table_get(ctx, str); - - memcpy(buf + pos, &str, sizeof(const char *)); - pos += sizeof(const char *); - } - - i_assert(pos == ctx->sort_element_size); - - ctx->callbacks->input_reset(ctx->func_context); - t_pop(); -} - -static struct mail_sort_context *qsort_context; - -static time_t get_time(enum mail_sort_type type, const unsigned char *buf, - struct mail_sort_context *ctx) -{ - time_t t; - - if ((ctx->cache_mask & type) == 0) { - return ctx->callbacks-> - input_time(type, *((unsigned int *) buf), - ctx->func_context); - } - - /* use memcpy() to avoid any alignment problems */ - memcpy(&t, buf + sizeof(unsigned int), sizeof(t)); - return t; -} - -static time_t get_uofft(enum mail_sort_type type, const unsigned char *buf, - struct mail_sort_context *ctx) -{ - uoff_t size; - - if ((ctx->cache_mask & type) == 0) { - return ctx->callbacks-> - input_uofft(type, *((unsigned int *) buf), - ctx->func_context); - } - - /* use memcpy() to avoid any alignment problems */ - memcpy(&size, buf + sizeof(unsigned int), sizeof(size)); - return size; -} -static const char *get_str(enum mail_sort_type type, const unsigned char *buf, - struct mail_sort_context *ctx) -{ - const char *str; - enum mail_sort_type type2; - int pos; - - if ((ctx->cache_mask & type) == 0) { - unsigned int id = *((unsigned int *) buf); - - if (type == MAIL_SORT_SUBJECT) { - str = ctx->callbacks->input_str(MAIL_SORT_SUBJECT, id, - ctx->func_context); - p_clear(ctx->temp_pool); - str = imap_get_base_subject_cased(ctx->temp_pool, - str, NULL); - } else { - str = ctx->callbacks->input_mailbox(type, id, - ctx->func_context); - if (str != NULL) - str = str_ucase(t_strdup_noconst(str)); - - } - return str; - } - - /* figure out where it is. pretty ugly. */ - type2 = (ctx->cache_mask & ~type); - - if (type2 == 0) - pos = 0; - else if (IS_SORT_TIME(type2)) - pos = sizeof(time_t); - else if (type2 == MAIL_SORT_SIZE) - pos = sizeof(uoff_t); - else { - if (type == MAIL_SORT_SUBJECT) - pos = sizeof(const char *); - else if (type2 != MAIL_SORT_SUBJECT && type > type2) - pos = sizeof(const char *); - else - pos = 0; - } - - /* use memcpy() to avoid any alignment problems */ - memcpy(&str, buf + pos + sizeof(unsigned int), sizeof(const char *)); - return str; -} - -static int mail_sort_qsort_func(const void *p1, const void *p2) -{ - enum mail_sort_type *output; - int ret, reverse = FALSE; - - output = qsort_context->output; - - t_push(); - - ret = 0; - for (; *output != MAIL_SORT_END && ret == 0; output++) { - if (*output == MAIL_SORT_REVERSE) { - reverse = !reverse; - continue; - } - - switch (*output) { - case MAIL_SORT_ARRIVAL: - case MAIL_SORT_DATE: { - time_t r1, r2; - - r1 = get_time(*output, p1, qsort_context); - r2 = get_time(*output, p2, qsort_context); - ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0; - break; - } - case MAIL_SORT_SIZE: { - uoff_t r1, r2; - - r1 = get_uofft(*output, p1, qsort_context); - r2 = get_uofft(*output, p2, qsort_context); - ret = r1 < r2 ? -1 : r1 > r2 ? 1 : 0; - break; - } - case MAIL_SORT_CC: - case MAIL_SORT_FROM: - case MAIL_SORT_TO: - case MAIL_SORT_SUBJECT: { - const char *s1, *s2; - - s1 = get_str(*output, p1, qsort_context); - s2 = get_str(*output, p2, qsort_context); - if (s1 == NULL) - ret = s2 == NULL ? 0 : -1; - else if (s2 == NULL) - ret = 1; - else - ret = strcmp(s1, s2); - break; - } - default: - i_unreached(); - } - - if (reverse) { - if (ret > 0) - ret = -1; - else if (ret < 0) - ret = 1; - } - - reverse = FALSE; - } - - qsort_context->callbacks->input_reset(qsort_context->func_context); - - t_pop(); - - return ret != 0 ? ret : - (*((unsigned int *) p1) < *((unsigned int *) p2) ? -1 : 1); -} - -static void mail_sort_flush(struct mail_sort_context *ctx) -{ - unsigned char *arr; - size_t i, count; - - qsort_context = ctx; - - arr = buffer_get_modifyable_data(ctx->sort_buffer, NULL); - count = buffer_get_used_size(ctx->sort_buffer) / ctx->sort_element_size; - qsort(arr, count, ctx->sort_element_size, mail_sort_qsort_func); - - for (i = 0; i < count; i++, arr += ctx->sort_element_size) { - unsigned int id = *((unsigned int *) arr); - - t_push(); - o_stream_send(ctx->outstream, " ", 1); - o_stream_send_str(ctx->outstream, dec2str(id)); - t_pop(); - } - - buffer_set_used_size(ctx->sort_buffer, 0); - - if (ctx->string_table != NULL) { - hash_clear(ctx->string_table, TRUE); - p_clear(ctx->str_pool); - } -} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/mail-sort.h --- a/src/lib-storage/mail-sort.h Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -#ifndef __MAIL_SORT_H -#define __MAIL_SORT_H - -#include "mail-storage.h" - -/* Maximum size for sort program, 2x for reverse + END */ -#define MAX_SORT_PROGRAM_SIZE (2*7 + 1) - -struct mail_sort_callbacks { - /* arrival, date */ - time_t (*input_time)(enum mail_sort_type type, unsigned int id, - void *context); - /* size */ - uoff_t (*input_uofft)(enum mail_sort_type type, unsigned int id, - void *context); - /* cc, from, to. Return the mailbox of the first address. */ - const char *(*input_mailbox)(enum mail_sort_type type, unsigned int id, - void *context); - /* subject */ - const char *(*input_str)(enum mail_sort_type type, unsigned int id, - void *context); - - /* done parsing this message, free all resources */ - void (*input_reset)(void *context); -}; - -/* input and output are arrays of sort programs ending with MAIL_SORT_END. - input specifies the order in which the messages are arriving to sorting. - It may be just MAIL_SORT_END if the order is random. The better the ordering - is known, the less memory is used. */ -struct mail_sort_context * -mail_sort_init(const enum mail_sort_type *input, enum mail_sort_type *output, - struct ostream *outstream, - const struct mail_sort_callbacks *callbacks, void *context); -void mail_sort_deinit(struct mail_sort_context *ctx); - -/* id is either UID or sequence number of message, whichever is preferred - in mail_sort_callbacks parameters. */ -void mail_sort_input(struct mail_sort_context *ctx, unsigned int id); - -#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/mail-storage.h --- a/src/lib-storage/mail-storage.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib-storage/mail-storage.h Mon Jan 20 16:52:51 2003 +0200 @@ -1,6 +1,8 @@ #ifndef __MAIL_STORAGE_H #define __MAIL_STORAGE_H +struct message_size; + #include "imap-util.h" enum mailbox_flags { @@ -38,6 +40,9 @@ }; enum mail_sort_type { +/* Maximum size for sort program, 2x for reverse + END */ +#define MAX_SORT_PROGRAM_SIZE (2*7 + 1) + MAIL_SORT_ARRIVAL = 0x0010, MAIL_SORT_CC = 0x0020, MAIL_SORT_DATE = 0x0040, @@ -57,11 +62,36 @@ MAIL_THREAD_REFERENCES }; +enum mail_fetch_field { + MAIL_FETCH_FLAGS = 0x0001, + MAIL_FETCH_MESSAGE_PARTS = 0x0002, + + MAIL_FETCH_RECEIVED_DATE = 0x0004, + MAIL_FETCH_DATE = 0x0008, + MAIL_FETCH_SIZE = 0x0010, + + MAIL_FETCH_STREAM_HEADER = 0x0020, + MAIL_FETCH_STREAM_BODY = 0x0040, + + /* specials: */ + MAIL_FETCH_IMAP_BODY = 0x1000, + MAIL_FETCH_IMAP_BODYSTRUCTURE = 0x2000, + MAIL_FETCH_IMAP_ENVELOPE = 0x4000 +}; + +struct mail_full_flags { + enum mail_flags flags; + + const char **custom_flags; + unsigned int custom_flags_count; +}; + struct mail_storage; struct mail_storage_callbacks; struct mailbox_status; -struct mail_fetch_data; struct mail_search_arg; +struct fetch_context; +struct search_context; typedef void (*mailbox_list_callback_t)(struct mail_storage *storage, const char *name, @@ -179,7 +209,7 @@ /* Update mail flags, calling update_flags callbacks. */ int (*update_flags)(struct mailbox *box, const char *messageset, int uidset, - enum mail_flags flags, const char *custom_flags[], + const struct mail_full_flags *flags, enum modify_type modify_type, int notify, int *all_found); @@ -187,27 +217,62 @@ int (*copy)(struct mailbox *box, struct mailbox *destbox, const char *messageset, int uidset); - /* Fetch wanted mail data. The results are written into output stream - in RFC2060 FETCH format. */ - int (*fetch)(struct mailbox *box, struct mail_fetch_data *fetch_data, - struct ostream *output, int *all_found); + /* Initialize new fetch request. wanted_fields isn't required, but it + can be used for optimizations. If *update_seen is TRUE, \Seen flag + is set for all fetched mails. *update_seen may be changed back to + FALSE if all mails are already seen, or if it's not possible to + change the flag (eg. read-only mailbox). */ + struct mail_fetch_context * + (*fetch_init)(struct mailbox *box, + enum mail_fetch_field wanted_fields, + int *update_seen, + const char *messageset, int uidset); + /* Deinitialize fetch request. all_found is set to TRUE if all of the + fetched messages were found (ie. not just deleted). */ + int (*fetch_deinit)(struct mail_fetch_context *ctx, int *all_found); + /* Fetch the next message. Returned mail object can be used until + the next call to fetch_next() or fetch_deinit(). */ + struct mail *(*fetch_next)(struct mail_fetch_context *ctx); + + /* Simplified fetching for a single UID or sequence. Must be called + between fetch_init() .. fetch_deinit() or + search_init() .. search_deinit() */ + struct mail *(*fetch_uid)(struct mailbox *box, unsigned int uid, + enum mail_fetch_field wanted_fields); + struct mail *(*fetch_seq)(struct mailbox *box, unsigned int seq, + enum mail_fetch_field wanted_fields); - /* Search wanted mail data. args contains the search criteria. - Results are written into output stream in RFC2060 SEARCH format. - If charset is NULL, the given search strings are matched without - any conversion. */ - int (*search)(struct mailbox *box, const char *charset, - struct mail_search_arg *args, - enum mail_sort_type *sorting, - enum mail_thread_type threading, - struct ostream *output, int uid_result); + /* Modify sort_program to specify a sort program acceptable for + search_init(). If server supports no sorting, it's simply set to + {MAIL_SORT_END}. */ + int (*search_get_sorting)(struct mailbox *box, + enum mail_sort_type *sort_program); + /* Initialize new search request. Search arguments are given so that + the storage can optimize the searching as it wants. + + If sort_program is non-NULL, it requests that the returned messages + are sorted by the given criteria. sort_program must have gone + through search_get_sorting(). + + wanted_fields and wanted_headers aren't required, but they can be + used for optimizations. */ + struct mail_search_context * + (*search_init)(struct mailbox *box, const char *charset, + struct mail_search_arg *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + const char *const wanted_headers[]); + /* Deinitialize search request. */ + int (*search_deinit)(struct mail_search_context *ctx); + /* Search the next message. Returned mail object can be used until + the next call to search_next() or search_deinit(). */ + struct mail *(*search_next)(struct mail_search_context *ctx); /* Save a new mail into mailbox. timezone_offset specifies the - timezone in minutes which internal_date was originally given + timezone in minutes which received_date was originally given with. */ - int (*save)(struct mailbox *box, enum mail_flags flags, - const char *custom_flags[], - time_t internal_date, int timezone_offset, + int (*save)(struct mailbox *box, const struct mail_full_flags *flags, + time_t received_date, int timezone_offset, struct istream *data, uoff_t data_size); /* Returns TRUE if mailbox is now in inconsistent state, meaning that @@ -224,6 +289,48 @@ unsigned int inconsistent:1; }; +struct mail { + /* always set */ + unsigned int seq; + unsigned int uid; + + unsigned int seen_updated:1; /* if update_seen was TRUE */ + + const struct mail_full_flags *(*get_flags)(struct mail *mail); + const struct message_part *(*get_parts)(struct mail *mail); + + /* Get the time message was received (IMAP INTERNALDATE). + Returns (time_t)-1 if error occured. */ + time_t (*get_received_date)(struct mail *mail); + /* Get the Date-header in mail. Timezone is in minutes. + Returns (time_t)-1 if error occured, 0 if field wasn't found or + couldn't be parsed. */ + time_t (*get_date)(struct mail *mail, int *timezone); + /* Get the full virtual size of mail (IMAP RFC822.SIZE). + Returns (uoff_t)-1 if error occured */ + uoff_t (*get_size)(struct mail *mail); + + /* Get value for single header field */ + const char *(*get_header)(struct mail *mail, const char *field); + + /* Returns the parsed address for given header field. */ + const struct message_address *(*get_address)(struct mail *mail, + const char *field); + /* Returns the first mailbox (RFC2822 local-part) field for given + address header field. */ + const char *(*get_first_mailbox)(struct mail *mail, const char *field); + + /* Returns input stream pointing to beginning of message header. + hdr_size and body_size are updated unless they're NULL. */ + struct istream *(*get_stream)(struct mail *mail, + struct message_size *hdr_size, + struct message_size *body_size); + + /* Get the any of the "special" fields. */ + const char *(*get_special)(struct mail *mail, + enum mail_fetch_field field); +}; + struct mailbox_status { unsigned int messages; unsigned int recent; @@ -272,34 +379,6 @@ }; -struct mail_fetch_data { - const char *messageset; - unsigned int uidset:1; - - unsigned int body:1; - unsigned int bodystructure:1; - unsigned int envelope:1; - unsigned int flags:1; - unsigned int internaldate:1; - unsigned int rfc822:1; - unsigned int rfc822_header:1; - unsigned int rfc822_size:1; - unsigned int rfc822_text:1; - unsigned int uid:1; - - struct mail_fetch_body_data *body_sections; -}; - -struct mail_fetch_body_data { - struct mail_fetch_body_data *next; - - const char *section; /* NOTE: always uppercased */ - uoff_t skip, max_size; /* if you don't want max_size, - set it to (uoff_t)-1 */ - unsigned int skip_set:1; - unsigned int peek:1; -}; - /* register all mail storages */ void mail_storage_register_all(void); diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/mail-thread.c --- a/src/lib-storage/mail-thread.c Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,894 +0,0 @@ -/* Copyright (C) 2002 Timo Sirainen */ - -/* - * Merge sort code in sort_nodes() is copyright 2001 Simon Tatham. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR - * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* Implementation of draft-ietf-imapext-thread-12 threading algorithm */ - -#include "lib.h" -#include "hash.h" -#include "ostream.h" -#include "str.h" -#include "message-tokenize.h" -#include "imap-base-subject.h" -#include "mail-thread.h" - -#include - -/* how much memory to allocate initially. these are very rough - approximations. */ -#define APPROX_MSG_COUNT 128 -#define APPROX_MSGID_SIZE 45 - -/* Try to buffer this much data before sending it to output stream. */ -#define OUTPUT_BUF_SIZE 2048 - -#define NODE_IS_DUMMY(node) ((node)->id == 0) -#define NODE_HAS_PARENT(ctx, node) \ - ((node)->parent != NULL && (node)->parent != &(ctx)->root_node) - -struct root_info { - char *base_subject; - unsigned int reply:1; - unsigned int sorted:1; -}; - -struct node { - struct node *parent, *first_child, *next; - - unsigned int id; - time_t sent_date; - - union { - char *msgid; - struct root_info *info; - } u; -}; - -struct mail_thread_context { - pool_t pool; - pool_t temp_pool; - - struct hash_table *msgid_hash; - struct hash_table *subject_hash; - - struct node root_node; - size_t root_count; /* not exact after prune_dummy_messages() */ - - const struct mail_sort_callbacks *callbacks; - void *callback_context; - - struct ostream *output; -}; - -struct mail_thread_context * -mail_thread_init(enum mail_thread_type type, struct ostream *output, - const struct mail_sort_callbacks *callbacks, - void *callback_context) -{ - struct mail_thread_context *ctx; - pool_t pool; - - if (type != MAIL_THREAD_REFERENCES) - i_fatal("Only REFERENCES threading supported"); - - pool = pool_alloconly_create("mail_thread_context", - sizeof(struct node) * APPROX_MSG_COUNT); - - ctx = p_new(pool, struct mail_thread_context, 1); - ctx->pool = pool; - ctx->temp_pool = pool_alloconly_create("mail_thread_context temp", - APPROX_MSG_COUNT * - APPROX_MSGID_SIZE); - ctx->msgid_hash = hash_create(default_pool, ctx->temp_pool, - APPROX_MSG_COUNT*2, str_hash, - (hash_cmp_callback_t)strcmp); - ctx->callbacks = callbacks; - ctx->callback_context = callback_context; - ctx->output = output; - return ctx; -} - -static void mail_thread_deinit(struct mail_thread_context *ctx) -{ - if (ctx->msgid_hash != NULL) - hash_destroy(ctx->msgid_hash); - if (ctx->subject_hash != NULL) - hash_destroy(ctx->subject_hash); - - pool_unref(ctx->temp_pool); - pool_unref(ctx->pool); -} - -static void add_root(struct mail_thread_context *ctx, struct node *node) -{ - node->parent = &ctx->root_node; - node->next = ctx->root_node.first_child; - ctx->root_node.first_child = node; - - ctx->root_count++; -} - -static struct node *create_node(struct mail_thread_context *ctx, - const char *msgid) -{ - struct node *node; - - node = p_new(ctx->pool, struct node, 1); - node->u.msgid = p_strdup(ctx->temp_pool, msgid); - - hash_insert(ctx->msgid_hash, node->u.msgid, node); - return node; -} - -static struct node *create_id_node(struct mail_thread_context *ctx, - unsigned int id, time_t sent_date) -{ - struct node *node; - - node = p_new(ctx->pool, struct node, 1); - node->id = id; - node->sent_date = sent_date; - - add_root(ctx, node); - return node; -} - -static struct node *update_message(struct mail_thread_context *ctx, - const char *msgid, time_t sent_date, - unsigned int id) -{ - struct node *node; - - if (msgid == NULL) - return create_id_node(ctx, id, sent_date); - - node = hash_lookup(ctx->msgid_hash, msgid); - if (node == NULL) { - /* first time we see this message */ - node = create_node(ctx, msgid); - node->id = id; - node->sent_date = sent_date; - return node; - } - - if (node->id == 0) { - /* seen before in references */ - node->id = id; - node->sent_date = sent_date; - } else { - /* duplicate */ - node = create_id_node(ctx, id, sent_date); - } - - return node; -} - -static int get_untokenized_msgid(const char **msgid_p, string_t *msgid) -{ - static const enum message_token stop_tokens[] = { '>', TOKEN_LAST }; - struct message_tokenizer *tok; - int valid_end; - - tok = message_tokenize_init((const unsigned char *) *msgid_p, - (size_t)-1, NULL, NULL); - message_tokenize_dot_token(tok, FALSE); /* just a minor speedup */ - - message_tokenize_get_string(tok, msgid, NULL, stop_tokens); - valid_end = message_tokenize_get(tok) == '>'; - - *msgid_p += message_tokenize_get_parse_position(tok); - message_tokenize_deinit(tok); - - if (valid_end) { - if (strchr(str_c(msgid), '@') != NULL) { - /* - valid message ID found */ - return TRUE; - } - } - - return FALSE; -} - -static const char *get_msgid(const char **msgid_p) -{ - const char *msgid = *msgid_p; - const char *p; - string_t *str = NULL; - int found_at; - - if (*msgid_p == NULL) - return NULL; - - for (;;) { - /* skip until '<' */ - while (*msgid != '<') { - if (*msgid == '\0') { - *msgid_p = msgid; - return NULL; - } - msgid++; - } - msgid++; - - /* check it through quickly to see if it's already normalized */ - p = msgid; found_at = FALSE; - for (;; p++) { - if ((unsigned char)*p >= 'A') /* matches most */ - continue; - - if (*p == '@') - found_at = TRUE; - if (*p == '>' || *p == '"' || *p == '(' || *p == ' ' || - *p == '\t' || *p == '\r' || *p == '\n') - break; - - if (*p == '\0') { - *msgid_p = p; - return NULL; - } - } - - if (*p == '>') { - *msgid_p = p+1; - if (found_at) - return t_strdup_until(msgid, p); - } else { - /* ok, do it the slow way */ - *msgid_p = msgid; - - if (str == NULL) { - /* allocate only once, so we don't leak - with multiple invalid message IDs */ - str = t_str_new(256); - } - if (get_untokenized_msgid(msgid_p, str)) - return str_c(str); - } - - /* invalid message id, see if there's another valid one */ - msgid = *msgid_p; - } -} - -static void unlink_child(struct mail_thread_context *ctx, - struct node *child, int add_to_root) -{ - struct node **node; - - node = &child->parent->first_child; - for (; *node != NULL; node = &(*node)->next) { - if (*node == child) { - *node = child->next; - break; - } - } - - child->next = NULL; - if (!add_to_root) - child->parent = NULL; - else - add_root(ctx, child); -} - -static int find_child(struct node *node, struct node *child) -{ - do { - if (node == child) - return TRUE; - - if (node->first_child != NULL) { - if (find_child(node->first_child, child)) - return TRUE; - } - - node = node->next; - } while (node != NULL); - - return FALSE; -} - -static void link_node(struct mail_thread_context *ctx, const char *parent_msgid, - struct node *child, int replace) -{ - struct node *parent, **node; - - if (NODE_HAS_PARENT(ctx, child) && !replace) { - /* already got a parent, don't want to replace it */ - return; - } - - parent = hash_lookup(ctx->msgid_hash, parent_msgid); - if (parent == NULL) - parent = create_node(ctx, parent_msgid); - - if (child->parent == parent) { - /* already have this parent, ignore */ - return; - } - - if (find_child(child, parent)) { - /* this would create a loop, not allowed */ - return; - } - - if (child->parent != NULL) - unlink_child(ctx, child, FALSE); - - /* link them */ - child->parent = parent; - - node = &parent->first_child; - while (*node != NULL) - node = &(*node)->next; - *node = child; -} - -static void link_message(struct mail_thread_context *ctx, - const char *parent_msgid, const char *child_msgid, - int replace) -{ - struct node *child; - - child = hash_lookup(ctx->msgid_hash, child_msgid); - if (child == NULL) - child = create_node(ctx, child_msgid); - - link_node(ctx, parent_msgid, child, replace); -} - -static int link_references(struct mail_thread_context *ctx, - struct node *node, const char *references) -{ - const char *parent_id, *child_id; - - parent_id = get_msgid(&references); - if (parent_id == NULL) - return FALSE; - - while ((child_id = get_msgid(&references)) != NULL) { - link_message(ctx, parent_id, child_id, FALSE); - parent_id = child_id; - } - - /* link the last message to us */ - link_node(ctx, parent_id, node, TRUE); - return TRUE; -} - -void mail_thread_input(struct mail_thread_context *ctx, unsigned int id, - const char *message_id, const char *in_reply_to, - const char *references, time_t sent_date) -{ - const char *refid; - struct node *node; - - i_assert(id > 0); - - node = update_message(ctx, get_msgid(&message_id), sent_date, id); - - /* link references */ - if (!link_references(ctx, node, references)) { - refid = get_msgid(&in_reply_to); - if (refid != NULL) - link_node(ctx, refid, node, TRUE); - else { - /* no references, make sure it's not linked */ - if (node != NULL && NODE_HAS_PARENT(ctx, node)) - unlink_child(ctx, node, TRUE); - } - } -} - -static struct node *find_last_child(struct node *node) -{ - node = node->first_child; - while (node->next != NULL) - node = node->next; - - return node; -} - -static struct node **promote_children(struct node **parent) -{ - struct node *new_parent, *old_parent, *child; - - old_parent = *parent; - new_parent = old_parent->parent; - - child = old_parent->first_child; - *parent = child; - - for (;;) { - child->parent = new_parent; - if (child->next == NULL) - break; - child = child->next; - } - - child->next = old_parent->next; - return &child->next; -} - -static void prune_dummy_messages(struct mail_thread_context *ctx, - struct node **node_p) -{ - struct node **a; - - a = node_p; - while (*node_p != NULL) { - if ((*node_p)->first_child != NULL) - prune_dummy_messages(ctx, &(*node_p)->first_child); - - if (NODE_IS_DUMMY(*node_p)) { - if ((*node_p)->first_child == NULL) { - /* no children -> delete */ - *node_p = (*node_p)->next; - continue; - } else if (NODE_HAS_PARENT(ctx, *node_p) || - (*node_p)->first_child->next == NULL) { - /* promote children to our level, - deleting the dummy node */ - node_p = promote_children(node_p); - continue; - } - } - - node_p = &(*node_p)->next; - } -} - -static int node_cmp(struct node *a, struct node *b) -{ - time_t date_a, date_b; - unsigned int id_a, id_b; - - date_a = a->id != 0 ? a->sent_date : a->first_child->sent_date; - date_b = b->id != 0 ? b->sent_date : b->first_child->sent_date; - - if (date_a != date_b && date_a != 0 && date_b != 0) - return date_a < date_b ? -1 : 1; - - id_a = a->id != 0 ? a->id : a->first_child->id; - id_b = b->id != 0 ? b->id : b->first_child->id; - return id_a < id_b ? -1 : 1; -} - -static struct node *sort_nodes(struct node *list) -{ - struct node *p, *q, *e, *tail; - size_t insize, nmerges, psize, qsize, i; - - i_assert(list != NULL); - - if (list->next == NULL) - return list; /* just one node */ - - insize = 1; - - for (;;) { - p = list; - list = NULL; - tail = NULL; - - nmerges = 0; /* count number of merges we do in this pass */ - while (p != 0) { - nmerges++; /* there exists a merge to be done */ - - /* step `insize' places along from p */ - q = p; - psize = 0; - for (i = 0; i < insize; i++) { - psize++; - q = q->next; - if (q == NULL) break; - } - - /* if q hasn't fallen off end, we have two lists to - merge */ - qsize = insize; - - /* now we have two lists; merge them */ - while (psize > 0 || (qsize > 0 && q != NULL)) { - /* decide whether next element of merge comes - from p or q */ - if (psize == 0) { - /* p is empty; e must come from q. */ - e = q; q = q->next; qsize--; - } else if (qsize == 0 || !q) { - /* q is empty; e must come from p. */ - e = p; p = p->next; psize--; - } else if (node_cmp(p, q) <= 0) { - /* First element of p is lower - (or same); e must come from p. */ - e = p; p = p->next; psize--; - } else { - /* First element of q is lower; - e must come from q. */ - e = q; q = q->next; qsize--; - } - - /* add the next element to the merged list */ - if (tail) - tail->next = e; - else - list = e; - tail = e; - } - - /* now p has stepped `insize' places along, - and q has too */ - p = q; - } - tail->next = NULL; - - /* If we have done only one merge, we're finished. */ - if (nmerges <= 1) { - /* allow for nmerges == 0, the empty list case */ - return list; - } - - /* Otherwise repeat, merging lists twice the size */ - insize *= 2; - } -} - -static void add_base_subject(struct mail_thread_context *ctx, - const char *subject, struct node *node) -{ - struct node *hash_node; - char *hash_subject; - void *key, *value; - int is_reply_or_forward; - - if (subject == NULL) - return; - - subject = imap_get_base_subject_cased(data_stack_pool, subject, - &is_reply_or_forward); - if (*subject == '\0') - return; - - if (!hash_lookup_full(ctx->subject_hash, subject, &key, &value)) { - hash_subject = p_strdup(ctx->temp_pool, subject); - hash_insert(ctx->subject_hash, hash_subject, node); - } else { - hash_subject = key; - hash_node = value; - - if (!NODE_IS_DUMMY(hash_node) && - (NODE_IS_DUMMY(node) || - (hash_node->u.info->reply && !is_reply_or_forward))) - hash_update(ctx->subject_hash, hash_subject, node); - } - - node->u.info->base_subject = hash_subject; - node->u.info->reply = is_reply_or_forward; -} - -static void gather_base_subjects(struct mail_thread_context *ctx) -{ - const struct mail_sort_callbacks *cb; - struct node *node; - unsigned int id; - - cb = ctx->callbacks; - ctx->subject_hash = - hash_create(default_pool, ctx->temp_pool, ctx->root_count * 2, - str_hash, (hash_cmp_callback_t)strcmp); - - node = ctx->root_node.first_child; - for (; node != NULL; node = node->next) { - if (!NODE_IS_DUMMY(node)) - id = node->id; - else { - /* sort children, use the first one's id */ - node->first_child = sort_nodes(node->first_child); - id = node->first_child->id; - - node->u.info->sorted = TRUE; - } - - t_push(); - - add_base_subject(ctx, cb->input_str(MAIL_SORT_SUBJECT, id, - ctx->callback_context), - node); - - cb->input_reset(ctx->callback_context); - t_pop(); - } -} - -static void reset_children_parent(struct node *parent) -{ - struct node *node; - - for (node = parent->first_child; node != NULL; node = node->next) - node->parent = parent; -} - -static void merge_subject_threads(struct mail_thread_context *ctx) -{ - struct node **node_p, *node, *hash_node; - char *base_subject; - - for (node_p = &ctx->root_node.first_child; *node_p != NULL; ) { - node = *node_p; - - if (node->u.info == NULL) { - /* deleted node */ - *node_p = node->next; - continue; - } - - /* (ii) If the thread subject is empty, skip this message. */ - base_subject = node->u.info->base_subject; - if (base_subject == NULL) { - node_p = &node->next; - continue; - } - - /* (iii) Lookup the message associated with this thread - subject in the subject table. */ - hash_node = hash_lookup(ctx->subject_hash, base_subject); - i_assert(hash_node != NULL); - - /* (iv) If the message in the subject table is the current - message, skip this message. */ - if (hash_node == node) { - node_p = &node->next; - continue; - } - - /* Otherwise, merge the current message with the one in the - subject table using the following rules: */ - - if (NODE_IS_DUMMY(node) && - NODE_IS_DUMMY(hash_node)) { - /* If both messages are dummies, append the current - message's children to the children of the message in - the subject table (the children of both messages - become siblings), and then delete the current - message. */ - find_last_child(hash_node)->next = node->first_child; - - *node_p = node->next; - hash_node->u.info->sorted = FALSE; - } else if (NODE_IS_DUMMY(hash_node) || - (node->u.info->reply && !hash_node->u.info->reply)) { - /* If the message in the subject table is a dummy - and the current message is not, make the current - message a child of the message in the subject table - (a sibling of its children). - - If the current message is a reply or forward and - the message in the subject table is not, make the - current message a child of the message in the - subject table (a sibling of its children). */ - *node_p = node->next; - - node->parent = hash_node; - node->next = hash_node->first_child; - hash_node->first_child = node; - - hash_node->u.info->sorted = FALSE; - } else { - /* Otherwise, create a new dummy message and make both - the current message and the message in the subject - table children of the dummy. Then replace the - message in the subject table with the dummy - message. */ - - /* create new nodes for the children - reusing - existing ones have problems since the other one - might have been handled already and we'd introduce - loops.. - - current node will be destroyed, hash_node will be - the dummy so we don't need to update hash */ - struct node *node1, *node2; - - node1 = p_new(ctx->pool, struct node, 1); - node2 = p_new(ctx->pool, struct node, 1); - - memcpy(node1, node, sizeof(struct node)); - memcpy(node2, hash_node, sizeof(struct node)); - - node1->parent = hash_node; - node2->parent = hash_node; - node1->next = node2; - node2->next = NULL; - - reset_children_parent(node1); - reset_children_parent(node2); - - hash_node->id = 0; - hash_node->first_child = node1; - hash_node->u.info->reply = FALSE; - hash_node->u.info->sorted = FALSE; - - node->first_child = NULL; - node->u.info = NULL; - *node_p = node->next; - } - } -} - -static void sort_root_nodes(struct mail_thread_context *ctx) -{ - struct node *node; - - /* sort the children first, they're needed to sort dummy root nodes */ - node = ctx->root_node.first_child; - for (; node != NULL; node = node->next) { - if (node->u.info == NULL) - continue; - - if (NODE_IS_DUMMY(node) && !node->u.info->sorted && - node->first_child != NULL) - node->first_child = sort_nodes(node->first_child); - } - - ctx->root_node.first_child = sort_nodes(ctx->root_node.first_child); -} - -static int send_nodes(struct mail_thread_context *ctx, - string_t *str, struct node *node) -{ - if (node->next == NULL && NODE_HAS_PARENT(ctx, node)) { - /* no siblings - special case to avoid extra paranthesis */ - if (node->first_child == NULL) - str_printfa(str, "%u", node->id); - else { - str_printfa(str, "%u ", node->id); - send_nodes(ctx, str, sort_nodes(node->first_child)); - } - return TRUE; - } - - while (node != NULL) { - if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) { - /* string getting full, flush it */ - if (!o_stream_send(ctx->output, - str_data(str), str_len(str))) - return FALSE; - str_truncate(str, 0); - } - - if (node->first_child == NULL) - str_printfa(str, "(%u)", node->id); - else { - str_printfa(str, "(%u ", node->id); - send_nodes(ctx, str, sort_nodes(node->first_child)); - str_append_c(str, ')'); - } - - node = node->next; - } - return TRUE; -} - -static void send_roots(struct mail_thread_context *ctx) -{ - struct node *node; - string_t *str; - - str = t_str_new(OUTPUT_BUF_SIZE); - str_append_c(str, ' '); - - /* sort root nodes again, they have been modified since the last time */ - sort_root_nodes(ctx); - - node = ctx->root_node.first_child; - for (; node != NULL; node = node->next) { - if (node->u.info == NULL) - continue; - - if (str_len(str) + MAX_INT_STRLEN*2 + 3 >= OUTPUT_BUF_SIZE) { - /* string getting full, flush it */ - if (!o_stream_send(ctx->output, - str_data(str), str_len(str))) - return; - str_truncate(str, 0); - } - - str_append_c(str, '('); - if (!NODE_IS_DUMMY(node)) - str_printfa(str, "%u", node->id); - - if (node->first_child != NULL) { - if (!NODE_IS_DUMMY(node)) - str_append_c(str, ' '); - - if (!node->u.info->sorted) { - node->first_child = - sort_nodes(node->first_child); - } - - if (!send_nodes(ctx, str, node->first_child)) - return; - } - - str_append_c(str, ')'); - } - - (void)o_stream_send(ctx->output, str_data(str), str_len(str)); -} - -static void save_root_cb(void *key __attr_unused__, void *value, void *context) -{ - struct mail_thread_context *ctx = context; - struct node *node = value; - - if (node->parent == NULL) - add_root(ctx, node); -} - -void mail_thread_finish(struct mail_thread_context *ctx) -{ - struct node *node; - - /* (2) save root nodes and drop the msgids */ - hash_foreach(ctx->msgid_hash, save_root_cb, ctx); - - /* drop the memory allocated for message-IDs and msgid_hash, - reuse their memory for base subjects */ - hash_destroy(ctx->msgid_hash); - ctx->msgid_hash = NULL; - - p_clear(ctx->temp_pool); - - if (ctx->root_node.first_child == NULL) { - /* no messages */ - mail_thread_deinit(ctx); - return; - } - - /* (3) */ - prune_dummy_messages(ctx, &ctx->root_node.first_child); - - /* initialize the node->u.info for all root nodes */ - node = ctx->root_node.first_child; - for (; node != NULL; node = node->next) - node->u.info = p_new(ctx->pool, struct root_info, 1); - - /* (4) */ - sort_root_nodes(ctx); - - /* (5) Gather together messages under the root that have the same - base subject text. */ - gather_base_subjects(ctx); - - /* (5.C) Merge threads with the same thread subject. */ - merge_subject_threads(ctx); - - /* (6) Sort and send replies */ - t_push(); - send_roots(ctx); - t_pop(); - - mail_thread_deinit(ctx); -} diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib-storage/mail-thread.h --- a/src/lib-storage/mail-thread.h Mon Jan 20 15:56:55 2003 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -#ifndef __MAIL_THREAD_H -#define __MAIL_THREAD_H - -#include "mail-storage.h" -#include "mail-sort.h" - -struct mail_thread_context; - -struct mail_thread_context * -mail_thread_init(enum mail_thread_type type, struct ostream *output, - const struct mail_sort_callbacks *callbacks, - void *callback_context); - -/* id is either UID or sequence number of message, whichever is preferred - in mail_thread_callbacks parameters. */ -void mail_thread_input(struct mail_thread_context *ctx, unsigned int id, - const char *message_id, const char *in_reply_to, - const char *references, time_t sent_date); - -void mail_thread_finish(struct mail_thread_context *ctx); - -#endif diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib/strfuncs.c --- a/src/lib/strfuncs.c Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib/strfuncs.c Mon Jan 20 16:52:51 2003 +0200 @@ -462,6 +462,16 @@ return str; } +int null_strcmp(const char *s1, const char *s2) +{ + if (s1 == NULL) + return s2 == NULL ? 0 : -1; + if (s2 == NULL) + return 1; + + return strcmp(s1, s1); +} + int memcasecmp(const void *p1, const void *p2, size_t size) { const unsigned char *s1 = p1; diff -r cbf096fbb9f0 -r 8028c4dcf38f src/lib/strfuncs.h --- a/src/lib/strfuncs.h Mon Jan 20 15:56:55 2003 +0200 +++ b/src/lib/strfuncs.h Mon Jan 20 16:52:51 2003 +0200 @@ -51,6 +51,7 @@ char *str_ucase(char *str); char *str_lcase(char *str); +int null_strcmp(const char *s1, const char *s2); int memcasecmp(const void *p1, const void *p2, size_t size); /* seprators is an array of separator characters, not a separator string. */