diff options
Diffstat (limited to '')
-rw-r--r-- | src/imap/imap-fetch.c | 1040 |
1 files changed, 1040 insertions, 0 deletions
diff --git a/src/imap/imap-fetch.c b/src/imap/imap-fetch.c new file mode 100644 index 0000000..db811f7 --- /dev/null +++ b/src/imap/imap-fetch.c @@ -0,0 +1,1040 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "array.h" +#include "buffer.h" +#include "istream.h" +#include "ostream.h" +#include "str.h" +#include "message-size.h" +#include "imap-date.h" +#include "imap-utf7.h" +#include "mail-search-build.h" +#include "imap-commands.h" +#include "imap-quote.h" +#include "imap-fetch.h" +#include "imap-util.h" + +#include <ctype.h> + +#define BODY_NIL_REPLY \ + "\"text\" \"plain\" NIL NIL NIL \"7bit\" 0 0" +#define ENVELOPE_NIL_REPLY \ + "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)" + +static ARRAY(struct imap_fetch_handler) fetch_handlers; + +static int imap_fetch_handler_cmp(const struct imap_fetch_handler *h1, + const struct imap_fetch_handler *h2) +{ + return strcmp(h1->name, h2->name); +} + +void imap_fetch_handlers_register(const struct imap_fetch_handler *handlers, + size_t count) +{ + array_append(&fetch_handlers, handlers, count); + array_sort(&fetch_handlers, imap_fetch_handler_cmp); +} + +void imap_fetch_handler_unregister(const char *name) +{ + const struct imap_fetch_handler *handler, *first_handler; + + first_handler = array_front(&fetch_handlers); + handler = imap_fetch_handler_lookup(name); + i_assert(handler != NULL); + array_delete(&fetch_handlers, handler - first_handler, 1); +} + +static int +imap_fetch_handler_bsearch(const char *name, const struct imap_fetch_handler *h) +{ + return strcmp(name, h->name); +} + +const struct imap_fetch_handler *imap_fetch_handler_lookup(const char *name) +{ + return array_bsearch(&fetch_handlers, name, imap_fetch_handler_bsearch); +} + +bool imap_fetch_init_handler(struct imap_fetch_init_context *init_ctx) +{ + const struct imap_fetch_handler *handler; + const char *lookup_name, *p; + + for (p = init_ctx->name; i_isalnum(*p) || *p == '-'; p++) ; + lookup_name = t_strdup_until(init_ctx->name, p); + + handler = array_bsearch(&fetch_handlers, lookup_name, + imap_fetch_handler_bsearch); + if (handler == NULL) { + init_ctx->error = t_strdup_printf("Unknown parameter: %s", + init_ctx->name); + return FALSE; + } + if (!handler->init(init_ctx)) { + i_assert(init_ctx->error != NULL); + return FALSE; + } + return TRUE; +} + +void imap_fetch_init_nofail_handler(struct imap_fetch_context *ctx, + bool (*init)(struct imap_fetch_init_context *)) +{ + struct imap_fetch_init_context init_ctx; + + i_zero(&init_ctx); + init_ctx.fetch_ctx = ctx; + init_ctx.pool = ctx->ctx_pool; + + if (!init(&init_ctx)) + i_unreached(); +} + +int imap_fetch_att_list_parse(struct client *client, pool_t pool, + const struct imap_arg *list, + struct imap_fetch_context **fetch_ctx_r, + const char **client_error_r) +{ + struct imap_fetch_init_context init_ctx; + const char *str; + + i_zero(&init_ctx); + init_ctx.fetch_ctx = imap_fetch_alloc(client, pool, "NOTIFY"); + init_ctx.pool = pool; + init_ctx.args = list; + + while (imap_arg_get_atom(init_ctx.args, &str)) { + init_ctx.name = t_str_ucase(str); + init_ctx.args++; + if (!imap_fetch_init_handler(&init_ctx)) { + *client_error_r = t_strconcat("Invalid fetch-att list: ", + init_ctx.error, NULL); + imap_fetch_free(&init_ctx.fetch_ctx); + return -1; + } + } + if (!IMAP_ARG_IS_EOL(init_ctx.args)) { + *client_error_r = "fetch-att list contains non-atoms."; + imap_fetch_free(&init_ctx.fetch_ctx); + return -1; + } + *fetch_ctx_r = init_ctx.fetch_ctx; + return 0; +} + +struct imap_fetch_context * +imap_fetch_alloc(struct client *client, pool_t pool, const char *reason) +{ + struct imap_fetch_context *ctx; + + ctx = p_new(pool, struct imap_fetch_context, 1); + ctx->client = client; + ctx->ctx_pool = pool; + ctx->reason = p_strdup(pool, reason); + pool_ref(pool); + + p_array_init(&ctx->all_headers, pool, 64); + p_array_init(&ctx->handlers, pool, 16); + p_array_init(&ctx->tmp_keywords, pool, + client->keywords.announce_count + 8); + return ctx; +} + +#undef imap_fetch_add_handler +void imap_fetch_add_handler(struct imap_fetch_init_context *ctx, + enum imap_fetch_handler_flags flags, + const char *nil_reply, + imap_fetch_handler_t *handler, void *context) +{ + /* partially because of broken clients, but also partially because + it potentially can make client implementations faster, we have a + buffered parameter which basically means that the handler promises + to write the output in fetch_ctx->state.cur_str. The cur_str is then + sent to client before calling any non-buffered handlers. + + We try to keep the handler registration order the same as the + client requested them. This is especially useful to get UID + returned first, which some clients rely on.. + */ + const struct imap_fetch_context_handler *ctx_handler; + struct imap_fetch_context_handler h; + + if (context == NULL) { + /* don't allow duplicate handlers */ + array_foreach(&ctx->fetch_ctx->handlers, ctx_handler) { + if (ctx_handler->handler == handler && + ctx_handler->context == NULL) + return; + } + } + + i_zero(&h); + h.handler = handler; + h.context = context; + h.buffered = (flags & IMAP_FETCH_HANDLER_FLAG_BUFFERED) != 0; + h.want_deinit = (flags & IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT) != 0; + h.name = p_strdup(ctx->pool, ctx->name); + h.nil_reply = p_strdup(ctx->pool, nil_reply); + + if (!h.buffered) + array_push_back(&ctx->fetch_ctx->handlers, &h); + else { + array_insert(&ctx->fetch_ctx->handlers, + ctx->fetch_ctx->buffered_handlers_count, &h, 1); + ctx->fetch_ctx->buffered_handlers_count++; + } +} + +static void +expunges_drop_known(struct mailbox *box, + const struct imap_fetch_qresync_args *qresync_args, + struct mailbox_transaction_context *trans, + ARRAY_TYPE(seq_range) *expunged_uids) +{ + struct mailbox_status status; + struct mail *mail; + const uint32_t *seqs, *uids; + unsigned int i, count; + + seqs = array_get(qresync_args->qresync_sample_seqset, &count); + uids = array_front(qresync_args->qresync_sample_uidset); + i_assert(array_count(qresync_args->qresync_sample_uidset) == count); + i_assert(count > 0); + + mailbox_get_open_status(box, STATUS_MESSAGES, &status); + mail = mail_alloc(trans, 0, NULL); + + /* FIXME: we could do removals from the middle as well */ + for (i = 0; i < count && seqs[i] <= status.messages; i++) { + mail_set_seq(mail, seqs[i]); + if (uids[i] != mail->uid) + break; + } + if (i > 0) + seq_range_array_remove_range(expunged_uids, 1, uids[i-1]); + mail_free(&mail); +} + +static int +get_expunges_fallback(struct mailbox *box, + const struct imap_fetch_qresync_args *qresync_args, + const ARRAY_TYPE(seq_range) *uid_filter_arr, + ARRAY_TYPE(seq_range) *expunged_uids) +{ + struct mailbox_transaction_context *trans; + struct mail_search_args *search_args; + struct mail_search_context *search_ctx; + struct mail *mail; + const struct seq_range *uid_filter; + struct mailbox_status status; + unsigned int i, count; + uint32_t next_uid; + int ret = 0; + + uid_filter = array_get(uid_filter_arr, &count); + i_assert(count > 0); + i = 0; + next_uid = uid_filter[0].seq1; + + /* search UIDs only in given range */ + search_args = mail_search_build_init(); + search_args->args = p_new(search_args->pool, struct mail_search_arg, 1); + search_args->args->type = SEARCH_UIDSET; + p_array_init(&search_args->args->value.seqset, search_args->pool, count); + array_append_array(&search_args->args->value.seqset, uid_filter_arr); + + trans = mailbox_transaction_begin(box, 0, "FETCH send VANISHED"); + search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL); + mail_search_args_unref(&search_args); + + while (mailbox_search_next(search_ctx, &mail)) { + if (mail->uid == next_uid) { + if (next_uid < uid_filter[i].seq2) + next_uid++; + else if (++i < count) + next_uid = uid_filter[i].seq1; + else + break; + } else { + /* next_uid .. mail->uid-1 are expunged */ + i_assert(mail->uid > next_uid); + while (mail->uid > uid_filter[i].seq2) { + seq_range_array_add_range(expunged_uids, + next_uid, + uid_filter[i].seq2); + i++; + i_assert(i < count); + next_uid = uid_filter[i].seq1; + } + if (next_uid != mail->uid) { + seq_range_array_add_range(expunged_uids, + next_uid, + mail->uid - 1); + } + if (uid_filter[i].seq2 != mail->uid) + next_uid = mail->uid + 1; + else if (++i < count) + next_uid = uid_filter[i].seq1; + else + break; + } + } + if (i < count) { + i_assert(next_uid <= uid_filter[i].seq2); + seq_range_array_add_range(expunged_uids, next_uid, + uid_filter[i].seq2); + i++; + } + for (; i < count; i++) { + seq_range_array_add_range(expunged_uids, uid_filter[i].seq1, + uid_filter[i].seq2); + } + + mailbox_get_open_status(box, STATUS_UIDNEXT, &status); + seq_range_array_remove_range(expunged_uids, status.uidnext, + (uint32_t)-1); + + if (mailbox_search_deinit(&search_ctx) < 0) + ret = -1; + + if (ret == 0 && qresync_args->qresync_sample_seqset != NULL && + array_is_created(qresync_args->qresync_sample_seqset)) + expunges_drop_known(box, qresync_args, trans, expunged_uids); + + (void)mailbox_transaction_commit(&trans); + return ret; +} + +int imap_fetch_send_vanished(struct client *client, struct mailbox *box, + const struct mail_search_args *search_args, + const struct imap_fetch_qresync_args *qresync_args) +{ + const struct mail_search_arg *uidarg = search_args->args; + const struct mail_search_arg *modseqarg = uidarg->next; + const ARRAY_TYPE(seq_range) *uid_filter; + uint64_t modseq; + ARRAY_TYPE(seq_range) expunged_uids_range; + string_t *str; + int ret = 0; + + i_assert(uidarg->type == SEARCH_UIDSET); + i_assert(modseqarg->type == SEARCH_MODSEQ); + + uid_filter = &uidarg->value.seqset; + modseq = modseqarg->value.modseq->modseq - 1; + + i_array_init(&expunged_uids_range, array_count(uid_filter)); + if (!mailbox_get_expunged_uids(box, modseq, uid_filter, &expunged_uids_range)) { + /* return all expunged UIDs */ + if (get_expunges_fallback(box, qresync_args, uid_filter, + &expunged_uids_range) < 0) { + array_clear(&expunged_uids_range); + ret = -1; + } + } + if (array_count(&expunged_uids_range) > 0) { + str = str_new(default_pool, 128); + str_append(str, "* VANISHED (EARLIER) "); + imap_write_seq_range(str, &expunged_uids_range); + str_append(str, "\r\n"); + o_stream_nsend(client->output, str_data(str), str_len(str)); + str_free(&str); + } + array_free(&expunged_uids_range); + return ret; +} + +static void imap_fetch_init(struct imap_fetch_context *ctx) +{ + if (ctx->initialized) + return; + ctx->initialized = TRUE; + + if (ctx->flags_update_seen && !ctx->flags_have_handler) { + ctx->flags_show_only_seen_changes = TRUE; + imap_fetch_init_nofail_handler(ctx, imap_fetch_flags_init); + } + if ((ctx->fetch_data & + (MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY)) != 0) + ctx->fetch_data |= MAIL_FETCH_NUL_STATE; +} + +void imap_fetch_begin(struct imap_fetch_context *ctx, struct mailbox *box, + struct mail_search_args *search_args) +{ + enum mailbox_transaction_flags trans_flags = + MAILBOX_TRANSACTION_FLAG_REFRESH; + struct mailbox_header_lookup_ctx *wanted_headers = NULL; + const char *const *headers; + + i_assert(!ctx->state.fetching); + + imap_fetch_init(ctx); + i_zero(&ctx->state); + + if (array_count(&ctx->all_headers) > 0 && + ((ctx->fetch_data & (MAIL_FETCH_STREAM_HEADER | + MAIL_FETCH_STREAM_BODY)) == 0)) { + array_append_zero(&ctx->all_headers); + + headers = array_front(&ctx->all_headers); + wanted_headers = mailbox_header_lookup_init(box, headers); + array_pop_back(&ctx->all_headers); + } + + if (ctx->flags_update_seen) { + /* Hide the implicit \Seen flag addition. Otherwise a separate + untagged FETCH FLAGS (\Seen) would be sent on top of the + one FLAGS (\Seen) already added in the main FETCH reply. + + We don't set this always, because some plugins might want + to do their own flag changes which we don't want hidden. + (Of course this isn't perfect since if implicit \Seen flags + are added, other flag changes are also hidden.) */ + trans_flags |= MAILBOX_TRANSACTION_FLAG_HIDE; + } + ctx->state.trans = mailbox_transaction_begin(box, trans_flags, + ctx->reason); + + mail_search_args_init(search_args, box, TRUE, + &ctx->client->search_saved_uidset); + ctx->state.search_ctx = + mailbox_search_init(ctx->state.trans, search_args, NULL, + ctx->fetch_data, wanted_headers); + ctx->state.cur_str = str_new(default_pool, 8192); + ctx->state.fetching = TRUE; + + mailbox_header_lookup_unref(&wanted_headers); +} + +static int imap_fetch_flush_buffer(struct imap_fetch_context *ctx) +{ + const unsigned char *data; + size_t len; + + data = str_data(ctx->state.cur_str); + len = str_len(ctx->state.cur_str); + + if (len == 0) + return 0; + + /* there's an extra space at the end if we added any fetch items + to buffer */ + if (data[len-1] == ' ') { + len--; + ctx->state.cur_first = FALSE; + } + ctx->state.line_partial = TRUE; + + if (o_stream_send(ctx->client->output, data, len) < 0) + return -1; + + str_truncate(ctx->state.cur_str, 0); + return 0; +} + +static int imap_fetch_send_nil_reply(struct imap_fetch_context *ctx) +{ + const struct imap_fetch_context_handler *handler; + + if (!ctx->state.cur_first) + str_append_c(ctx->state.cur_str, ' '); + + handler = array_idx(&ctx->handlers, ctx->state.cur_handler); + str_printfa(ctx->state.cur_str, "%s %s ", + handler->name, handler->nil_reply); + + if (!handler->buffered) { + if (imap_fetch_flush_buffer(ctx) < 0) + return -1; + } + return 0; +} + +static void imap_fetch_fix_empty_reply(struct imap_fetch_context *ctx) +{ + if (ctx->state.line_partial && ctx->state.cur_first) { + /* we've flushed an empty "FETCH (" reply so + far. we can't take it back, but RFC 3501 + doesn't allow returning empty "FETCH ()" + either, so just add the current message's + UID there. */ + str_printfa(ctx->state.cur_str, "UID %u ", + ctx->state.cur_mail->uid); + } +} + +static bool imap_fetch_cur_failed(struct imap_fetch_context *ctx) +{ + ctx->failures = TRUE; + if (ctx->client->set->parsed_fetch_failure == + IMAP_CLIENT_FETCH_FAILURE_DISCONNECT_IMMEDIATELY) + return FALSE; + + if (!array_is_created(&ctx->fetch_failed_uids)) + p_array_init(&ctx->fetch_failed_uids, ctx->ctx_pool, 8); + seq_range_array_add(&ctx->fetch_failed_uids, ctx->state.cur_mail->uid); + + if (ctx->error == MAIL_ERROR_NONE) { + /* preserve the first error, since it may change in storage. */ + ctx->errstr = p_strdup(ctx->ctx_pool, + mailbox_get_last_error(ctx->state.cur_mail->box, &ctx->error)); + } + return TRUE; +} + +static int imap_fetch_more_int(struct imap_fetch_context *ctx, bool cancel) +{ + struct imap_fetch_state *state = &ctx->state; + struct client *client = ctx->client; + const struct imap_fetch_context_handler *handlers; + unsigned int count; + int ret; + + if (state->cont_handler != NULL) { + ret = state->cont_handler(ctx); + if (ret == 0) + return 0; + + if (ret < 0) { + if (client->output->closed) + return -1; + + if (state->cur_mail->expunged) { + /* not an error, just lost it. */ + state->skipped_expunged_msgs = TRUE; + if (imap_fetch_send_nil_reply(ctx) < 0) + return -1; + } else { + if (!imap_fetch_cur_failed(ctx)) + return -1; + } + } + + state->cont_handler = NULL; + state->cur_handler++; + if (state->cur_input != NULL) + i_stream_unref(&state->cur_input); + } + + handlers = array_get(&ctx->handlers, &count); + for (;;) { + if (o_stream_get_buffer_used_size(client->output) >= + CLIENT_OUTPUT_OPTIMAL_SIZE) { + ret = o_stream_flush(client->output); + if (ret <= 0) + return ret; + } + + if (state->cur_mail == NULL) { + if (cancel) + return 1; + + if (!mailbox_search_next(state->search_ctx, + &state->cur_mail)) + break; + + str_printfa(state->cur_str, "* %u FETCH (", + state->cur_mail->seq); + ctx->fetched_mails_count++; + state->cur_first = TRUE; + state->cur_str_prefix_size = str_len(state->cur_str); + i_assert(!state->line_partial); + } + + for (; state->cur_handler < count; state->cur_handler++) { + if (str_len(state->cur_str) > 0 && + !handlers[state->cur_handler].buffered) { + /* first non-buffered handler. + flush the buffer. */ + if (imap_fetch_flush_buffer(ctx) < 0) + return -1; + } + + i_assert(state->cur_input == NULL); + T_BEGIN { + const struct imap_fetch_context_handler *h = + &handlers[state->cur_handler]; + + ret = h->handler(ctx, state->cur_mail, + h->context); + } T_END; + + if (ret == 0) + return 0; + + if (ret < 0) { + if (state->cur_mail->expunged) { + /* not an error, just lost it. */ + state->skipped_expunged_msgs = TRUE; + if (imap_fetch_send_nil_reply(ctx) < 0) + return -1; + } else { + i_assert(ret < 0 || + state->cont_handler != NULL); + if (!imap_fetch_cur_failed(ctx)) + return -1; + } + } + + state->cont_handler = NULL; + if (state->cur_input != NULL) + i_stream_unref(&state->cur_input); + } + + imap_fetch_fix_empty_reply(ctx); + if (str_len(state->cur_str) > 0 && + (state->line_partial || + str_len(state->cur_str) != state->cur_str_prefix_size)) { + /* no non-buffered handlers */ + if (imap_fetch_flush_buffer(ctx) < 0) + return -1; + } + + if (state->line_partial) + o_stream_nsend(client->output, ")\r\n", 3); + client->last_output = ioloop_time; + + state->cur_mail = NULL; + state->cur_handler = 0; + state->line_partial = FALSE; + } + + return ctx->failures ? -1 : 1; +} + +int imap_fetch_more(struct imap_fetch_context *ctx, + struct client_command_context *cmd) +{ + int ret; + + i_assert(ctx->client->output_cmd_lock == NULL || + ctx->client->output_cmd_lock == cmd); + + ret = imap_fetch_more_int(ctx, cmd->cancel); + if (ret < 0) + ctx->state.failed = TRUE; + if (ctx->state.line_partial) { + /* nothing can be sent until FETCH is finished */ + ctx->client->output_cmd_lock = cmd; + } + if (cmd->cancel && ctx->client->output_cmd_lock != NULL) { + /* canceling didn't really work. we must not output + anything anymore. */ + if (!ctx->client->destroyed) + client_disconnect(ctx->client, "Failed to cancel FETCH"); + ctx->client->output_cmd_lock = NULL; + } + return ret; +} + +int imap_fetch_more_no_lock_update(struct imap_fetch_context *ctx) +{ + int ret; + + ret = imap_fetch_more_int(ctx, FALSE); + if (ret < 0) { + ctx->state.failed = TRUE; + if (ctx->state.line_partial) { + /* we can't send any more replies to client, because + the FETCH reply wasn't fully sent. */ + client_disconnect(ctx->client, + "NOTIFY failed in the middle of FETCH reply"); + } + } + return ret; +} + +int imap_fetch_end(struct imap_fetch_context *ctx) +{ + struct imap_fetch_state *state = &ctx->state; + + if (ctx->state.fetching) { + ctx->state.fetching = FALSE; + if (state->line_partial) { + imap_fetch_fix_empty_reply(ctx); + if (imap_fetch_flush_buffer(ctx) < 0) + state->failed = TRUE; + if (o_stream_send(ctx->client->output, ")\r\n", 3) < 0) + state->failed = TRUE; + } + } + ctx->client->output_cmd_lock = NULL; + + str_free(&state->cur_str); + + i_stream_unref(&state->cur_input); + + if (state->search_ctx != NULL) { + if (mailbox_search_deinit(&state->search_ctx) < 0) + state->failed = TRUE; + } + + if (state->trans != NULL) { + /* even if something failed, we want to commit changes to + cache, as well as possible \Seen flag changes for FETCH + replies we returned so far. */ + if (mailbox_transaction_commit(&state->trans) < 0) + state->failed = TRUE; + } + return state->failed ? -1 : 0; +} + +void imap_fetch_free(struct imap_fetch_context **_ctx) +{ + struct imap_fetch_context *ctx = *_ctx; + const struct imap_fetch_context_handler *handler; + + *_ctx = NULL; + + (void)imap_fetch_end(ctx); + + array_foreach(&ctx->handlers, handler) { + if (handler->want_deinit) + handler->handler(ctx, NULL, handler->context); + } + pool_unref(&ctx->ctx_pool); +} + +static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + const char *body; + + if (mail_get_special(mail, MAIL_FETCH_IMAP_BODY, &body) < 0) + return -1; + + if (ctx->state.cur_first) + ctx->state.cur_first = FALSE; + else { + if (o_stream_send(ctx->client->output, " ", 1) < 0) + return -1; + } + + if (o_stream_send(ctx->client->output, "BODY (", 6) < 0 || + o_stream_send_str(ctx->client->output, body) < 0 || + o_stream_send(ctx->client->output, ")", 1) < 0) + return -1; + return 1; +} + +static bool fetch_body_init(struct imap_fetch_init_context *ctx) +{ + if (ctx->name[4] == '\0') { + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_BODY; + imap_fetch_add_handler(ctx, 0, "("BODY_NIL_REPLY")", + fetch_body, NULL); + return TRUE; + } + return imap_fetch_body_section_init(ctx); +} + +static int fetch_bodystructure(struct imap_fetch_context *ctx, + struct mail *mail, void *context ATTR_UNUSED) +{ + const char *bodystructure; + + if (mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, + &bodystructure) < 0) + return -1; + + if (ctx->state.cur_first) + ctx->state.cur_first = FALSE; + else { + if (o_stream_send(ctx->client->output, " ", 1) < 0) + return -1; + } + + if (o_stream_send(ctx->client->output, "BODYSTRUCTURE (", 15) < 0 || + o_stream_send_str(ctx->client->output, bodystructure) < 0 || + o_stream_send(ctx->client->output, ")", 1) < 0) + return -1; + + return 1; +} + +static bool fetch_bodystructure_init(struct imap_fetch_init_context *ctx) +{ + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE; + imap_fetch_add_handler(ctx, 0, "("BODY_NIL_REPLY" NIL NIL NIL NIL)", + fetch_bodystructure, NULL); + return TRUE; +} + +static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + const char *envelope; + + if (mail_get_special(mail, MAIL_FETCH_IMAP_ENVELOPE, &envelope) < 0) + return -1; + + if (ctx->state.cur_first) + ctx->state.cur_first = FALSE; + else { + if (o_stream_send(ctx->client->output, " ", 1) < 0) + return -1; + } + + if (o_stream_send(ctx->client->output, "ENVELOPE (", 10) < 0 || + o_stream_send_str(ctx->client->output, envelope) < 0 || + o_stream_send(ctx->client->output, ")", 1) < 0) + return -1; + return 1; +} + +static bool fetch_envelope_init(struct imap_fetch_init_context *ctx) +{ + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_IMAP_ENVELOPE; + imap_fetch_add_handler(ctx, 0, ENVELOPE_NIL_REPLY, + fetch_envelope, NULL); + return TRUE; +} + +static int fetch_flags(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + enum mail_flags flags; + const char *const *keywords; + + flags = mail_get_flags(mail); + if (ctx->flags_update_seen && (flags & MAIL_SEEN) == 0 && + !mailbox_is_readonly(mail->box)) { + /* Add \Seen flag */ + ctx->state.seen_flags_changed = TRUE; + flags |= MAIL_SEEN; + mail_update_flags(mail, MODIFY_ADD, MAIL_SEEN); + } else if (ctx->flags_show_only_seen_changes) { + return 1; + } + + keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords, + mail_get_keyword_indexes(mail)); + + str_append(ctx->state.cur_str, "FLAGS ("); + imap_write_flags(ctx->state.cur_str, flags, keywords); + str_append(ctx->state.cur_str, ") "); + return 1; +} + +bool imap_fetch_flags_init(struct imap_fetch_init_context *ctx) +{ + ctx->fetch_ctx->flags_have_handler = TRUE; + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_FLAGS; + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + "()", fetch_flags, NULL); + return TRUE; +} + +static int fetch_internaldate(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + time_t date; + + if (mail_get_received_date(mail, &date) < 0) + return -1; + + str_printfa(ctx->state.cur_str, "INTERNALDATE \"%s\" ", + imap_to_datetime(date)); + return 1; +} + +static bool fetch_internaldate_init(struct imap_fetch_init_context *ctx) +{ + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_RECEIVED_DATE; + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + "\"01-Jan-1970 00:00:00 +0000\"", + fetch_internaldate, NULL); + return TRUE; +} + +static int fetch_modseq(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + uint64_t modseq; + + modseq = mail_get_modseq(mail); + if (ctx->client->highest_fetch_modseq < modseq) + ctx->client->highest_fetch_modseq = modseq; + str_printfa(ctx->state.cur_str, "MODSEQ (%"PRIu64") ", modseq); + return 1; +} + +bool imap_fetch_modseq_init(struct imap_fetch_init_context *ctx) +{ + if (ctx->fetch_ctx->client->nonpermanent_modseqs) { + ctx->error = "FETCH MODSEQ can't be used with non-permanent modseqs"; + return FALSE; + } + client_enable(ctx->fetch_ctx->client, imap_feature_condstore); + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + NULL, fetch_modseq, NULL); + return TRUE; +} + +static int fetch_uid(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + str_printfa(ctx->state.cur_str, "UID %u ", mail->uid); + return 1; +} + +bool imap_fetch_uid_init(struct imap_fetch_init_context *ctx) +{ + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + NULL, fetch_uid, NULL); + return TRUE; +} + +static int imap_fetch_savedate(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + time_t date; + int ret; + + ret = mail_get_save_date(mail, &date); + if (ret < 0) + return -1; + + if (ret == 0) + str_append(ctx->state.cur_str, "SAVEDATE NIL "); + else { + str_printfa(ctx->state.cur_str, "SAVEDATE \"%s\" ", + imap_to_datetime(date)); + } + return 1; +} + +static bool imap_fetch_savedate_init(struct imap_fetch_init_context *ctx) +{ + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_SAVE_DATE; + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + "NIL", imap_fetch_savedate, NULL); + return TRUE; +} + +static int fetch_guid(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + const char *value; + + if (mail_get_special(mail, MAIL_FETCH_GUID, &value) < 0) + return -1; + + str_append(ctx->state.cur_str, "X-GUID "); + imap_append_astring(ctx->state.cur_str, value); + str_append_c(ctx->state.cur_str, ' '); + return 1; +} + +static bool fetch_guid_init(struct imap_fetch_init_context *ctx) +{ + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_GUID; + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + "", fetch_guid, NULL); + return TRUE; +} + +static int fetch_x_mailbox(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + const char *name; + string_t *mutf7_name; + + if (mail_get_special(mail, MAIL_FETCH_MAILBOX_NAME, &name) < 0) { + /* This can happen with virtual mailbox if the backend mail + is expunged. */ + return -1; + } + + mutf7_name = t_str_new(strlen(name)*2); + if (imap_utf8_to_utf7(name, mutf7_name) < 0) + i_panic("FETCH: Mailbox name not UTF-8: %s", name); + + str_append(ctx->state.cur_str, "X-MAILBOX "); + imap_append_astring(ctx->state.cur_str, str_c(mutf7_name)); + str_append_c(ctx->state.cur_str, ' '); + return 1; +} + +static bool fetch_x_mailbox_init(struct imap_fetch_init_context *ctx) +{ + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + NULL, fetch_x_mailbox, NULL); + return TRUE; +} + +static int fetch_x_real_uid(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + struct mail *real_mail; + + if (mail_get_backend_mail(mail, &real_mail) < 0) + return -1; + str_printfa(ctx->state.cur_str, "X-REAL-UID %u ", real_mail->uid); + return 1; +} + +static bool fetch_x_real_uid_init(struct imap_fetch_init_context *ctx) +{ + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + NULL, fetch_x_real_uid, NULL); + return TRUE; +} + +static int fetch_x_savedate(struct imap_fetch_context *ctx, struct mail *mail, + void *context ATTR_UNUSED) +{ + time_t date; + + if (mail_get_save_date(mail, &date) < 0) + return -1; + + str_printfa(ctx->state.cur_str, "X-SAVEDATE \"%s\" ", + imap_to_datetime(date)); + return 1; +} + +static bool fetch_x_savedate_init(struct imap_fetch_init_context *ctx) +{ + ctx->fetch_ctx->fetch_data |= MAIL_FETCH_SAVE_DATE; + imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED, + "\"01-Jan-1970 00:00:00 +0000\"", + fetch_x_savedate, NULL); + return TRUE; +} + +static const struct imap_fetch_handler +imap_fetch_default_handlers[] = { + { "BINARY", imap_fetch_binary_init }, + { "BODY", fetch_body_init }, + { "BODYSTRUCTURE", fetch_bodystructure_init }, + { "ENVELOPE", fetch_envelope_init }, + { "FLAGS", imap_fetch_flags_init }, + { "INTERNALDATE", fetch_internaldate_init }, + { "MODSEQ", imap_fetch_modseq_init }, + { "PREVIEW", imap_fetch_preview_init }, + { "RFC822", imap_fetch_rfc822_init }, + { "SNIPPET", imap_fetch_snippet_init }, + { "UID", imap_fetch_uid_init }, + { "SAVEDATE", imap_fetch_savedate_init }, + { "X-GUID", fetch_guid_init }, + { "X-MAILBOX", fetch_x_mailbox_init }, + { "X-REAL-UID", fetch_x_real_uid_init }, + { "X-SAVEDATE", fetch_x_savedate_init } +}; + +void imap_fetch_handlers_init(void) +{ + i_array_init(&fetch_handlers, 32); + imap_fetch_handlers_register(imap_fetch_default_handlers, + N_ELEMENTS(imap_fetch_default_handlers)); +} + +void imap_fetch_handlers_deinit(void) +{ + array_free(&fetch_handlers); +} |