diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/imap/cmd-getmetadata.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/imap/cmd-getmetadata.c')
-rw-r--r-- | src/imap/cmd-getmetadata.c | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/src/imap/cmd-getmetadata.c b/src/imap/cmd-getmetadata.c new file mode 100644 index 0000000..ec23135 --- /dev/null +++ b/src/imap/cmd-getmetadata.c @@ -0,0 +1,559 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "mail-storage-private.h" +#include "str.h" +#include "istream.h" +#include "istream-sized.h" +#include "ostream.h" +#include "mailbox-list-iter.h" +#include "imap-utf7.h" +#include "imap-quote.h" +#include "imap-metadata.h" + +struct imap_getmetadata_context { + struct client_command_context *cmd; + + struct mailbox *box; + struct imap_metadata_transaction *trans; + struct mailbox_list_iterate_context *list_iter; + + ARRAY_TYPE(const_string) entries; + uint32_t maxsize; + uoff_t largest_seen_size; + unsigned int depth; + + struct istream *cur_stream; + + struct imap_metadata_iter *iter; + string_t *iter_entry_prefix; + + string_t *delayed_errors; + string_t *last_error_str; + enum mail_error last_error; + + unsigned int entry_idx; + bool first_entry_sent; +}; + +static bool +cmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx); + +static bool +cmd_getmetadata_parse_options(struct imap_getmetadata_context *ctx, + const struct imap_arg *options) +{ + const char *value; + + while (!IMAP_ARG_IS_EOL(options)) { + if (imap_arg_atom_equals(options, "MAXSIZE")) { + options++; + if (!imap_arg_get_atom(options, &value) || + str_to_uint32(value, &ctx->maxsize) < 0) { + client_send_command_error(ctx->cmd, + "Invalid value for MAXSIZE option"); + return FALSE; + } + } else if (imap_arg_atom_equals(options, "DEPTH")) { + options++; + if (!imap_arg_get_atom(options, &value)) { + client_send_command_error(ctx->cmd, + "Invalid value for DEPTH option"); + return FALSE; + } + if (strcmp(value, "0") == 0) + ctx->depth = 0; + else if (strcmp(value, "1") == 0) + ctx->depth = 1; + else if (strcmp(value, "infinity") == 0) + ctx->depth = UINT_MAX; + else { + client_send_command_error(ctx->cmd, + "Invalid value for DEPTH option"); + return FALSE; + } + } else { + client_send_command_error(ctx->cmd, "Unknown option"); + return FALSE; + } + options++; + } + return TRUE; +} + +static bool +imap_metadata_parse_entry_names(struct imap_getmetadata_context *ctx, + const struct imap_arg *entries) +{ + const char *value, *client_error; + + p_array_init(&ctx->entries, ctx->cmd->pool, 4); + for (; !IMAP_ARG_IS_EOL(entries); entries++) { + if (!imap_arg_get_astring(entries, &value)) { + client_send_command_error(ctx->cmd, "Entry isn't astring"); + return FALSE; + } + if (!imap_metadata_verify_entry_name(value, &client_error)) { + client_send_command_error(ctx->cmd, client_error); + return FALSE; + } + + /* names are case-insensitive so we'll always lowercase them */ + value = p_strdup(ctx->cmd->pool, t_str_lcase(value)); + array_push_back(&ctx->entries, &value); + } + return TRUE; +} + +static string_t * +metadata_add_entry(struct imap_getmetadata_context *ctx, const char *entry) +{ + string_t *str; + + str = t_str_new(64); + if (!ctx->first_entry_sent) { + string_t *mailbox_mutf7 = t_str_new(64); + + ctx->first_entry_sent = TRUE; + str_append(str, "* METADATA "); + if (ctx->box == NULL) { + /* server metadata reply */ + str_append(str, "\"\""); + } else { + if (imap_utf8_to_utf7(mailbox_get_vname(ctx->box), mailbox_mutf7) < 0) + i_unreached(); + imap_append_astring(str, str_c(mailbox_mutf7)); + } + str_append(str, " ("); + + /* nothing can be sent until untagged METADATA is finished */ + ctx->cmd->client->output_cmd_lock = ctx->cmd; + } else { + str_append_c(str, ' '); + } + imap_append_astring(str, entry); + return str; +} + +static void +cmd_getmetadata_send_nil_reply(struct imap_getmetadata_context *ctx, + const char *entry) +{ + string_t *str; + + /* client requested a specific entry that didn't exist. + we must return it as NIL. */ + str = metadata_add_entry(ctx, entry); + str_append(str, " NIL"); + o_stream_nsend(ctx->cmd->client->output, str_data(str), str_len(str)); +} + +static void +cmd_getmetadata_handle_error_str(struct imap_getmetadata_context *ctx, + const char *error_string, + enum mail_error error) +{ + if (str_len(ctx->last_error_str) > 0) { + str_append(ctx->delayed_errors, "* NO "); + str_append_str(ctx->delayed_errors, ctx->last_error_str); + str_append(ctx->delayed_errors, "\r\n"); + str_truncate(ctx->last_error_str, 0); + } + str_append(ctx->last_error_str, error_string); + ctx->last_error = error; +} + +static bool +cmd_getmetadata_handle_error(struct imap_getmetadata_context *ctx) +{ + const char *error_string; + enum mail_error error; + + error_string = imap_metadata_transaction_get_last_error(ctx->trans, &error); + if (error == MAIL_ERROR_NOTPOSSIBLE && ctx->depth > 0) { + /* Using DEPTH to iterate children with imap_metadata=no. + Don't return an error, since some of the entries could be + returned successfully. */ + return FALSE; + } + + cmd_getmetadata_handle_error_str(ctx, error_string, error); + return TRUE; +} + +static void cmd_getmetadata_send_entry(struct imap_getmetadata_context *ctx, + const char *entry, bool require_reply) +{ + struct client *client = ctx->cmd->client; + struct mail_attribute_value value; + uoff_t value_len; + string_t *str; + + if (imap_metadata_get_stream(ctx->trans, entry, &value) < 0) { + if (cmd_getmetadata_handle_error(ctx)) + return; + } + + if (value.value != NULL) + value_len = strlen(value.value); + else if (value.value_stream != NULL) { + if (i_stream_get_size(value.value_stream, TRUE, &value_len) < 0) { + e_error(client->event, + "GETMETADATA %s: i_stream_get_size(%s) failed: %s", entry, + i_stream_get_name(value.value_stream), + i_stream_get_error(value.value_stream)); + i_stream_unref(&value.value_stream); + cmd_getmetadata_handle_error_str(ctx, + MAIL_ERRSTR_CRITICAL_MSG, MAIL_ERROR_TEMP); + return; + } + } else { + /* skip nonexistent entries */ + if (require_reply) + cmd_getmetadata_send_nil_reply(ctx, entry); + return; + } + + if (value_len > ctx->maxsize) { + /* value length is larger than specified MAXSIZE, + skip this entry */ + if (ctx->largest_seen_size < value_len) + ctx->largest_seen_size = value_len; + i_stream_unref(&value.value_stream); + return; + } + + str = metadata_add_entry(ctx, entry); + if (value.value != NULL) { + str_printfa(str, " {%"PRIuUOFF_T"}\r\n%s", value_len, value.value); + o_stream_nsend(client->output, str_data(str), str_len(str)); + } else { + str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", value_len); + o_stream_nsend(client->output, str_data(str), str_len(str)); + + ctx->cur_stream = i_stream_create_sized(value.value_stream, value_len); + i_stream_unref(&value.value_stream); + } +} + +static bool +cmd_getmetadata_stream_continue(struct imap_getmetadata_context *ctx) +{ + enum ostream_send_istream_result res; + + o_stream_set_max_buffer_size(ctx->cmd->client->output, 0); + res = o_stream_send_istream(ctx->cmd->client->output, ctx->cur_stream); + o_stream_set_max_buffer_size(ctx->cmd->client->output, SIZE_MAX); + + switch (res) { + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + return TRUE; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + i_unreached(); + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + return FALSE; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + e_error(ctx->cmd->client->event, "read(%s) failed: %s", + i_stream_get_name(ctx->cur_stream), + i_stream_get_error(ctx->cur_stream)); + client_disconnect(ctx->cmd->client, + "Internal GETMETADATA failure"); + return TRUE; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + /* client disconnected */ + return TRUE; + } + i_unreached(); +} + +static int +cmd_getmetadata_send_entry_tree(struct imap_getmetadata_context *ctx, + const char *entry) +{ + struct client *client = ctx->cmd->client; + + if (o_stream_get_buffer_used_size(client->output) >= + CLIENT_OUTPUT_OPTIMAL_SIZE) { + if (o_stream_flush(client->output) <= 0) { + o_stream_set_flush_pending(client->output, TRUE); + return 0; + } + } + + if (ctx->iter != NULL) { + const char *subentry; + + /* DEPTH iteration */ + do { + subentry = imap_metadata_iter_next(ctx->iter); + if (subentry == NULL) { + /* iteration finished, get to the next entry */ + if (imap_metadata_iter_deinit(&ctx->iter) < 0) { + if (!cmd_getmetadata_handle_error(ctx)) + i_unreached(); + } + return -1; + } + } while (ctx->depth == 1 && strchr(subentry, '/') != NULL); + entry = t_strconcat(str_c(ctx->iter_entry_prefix), subentry, NULL); + } + /* send NIL only on depth 0 query */ + cmd_getmetadata_send_entry(ctx, entry, ctx->depth == 0); + + if (ctx->cur_stream != NULL) { + if (!cmd_getmetadata_stream_continue(ctx)) + return 0; + i_stream_unref(&ctx->cur_stream); + } + + if (ctx->iter != NULL) { + /* already iterating the entry */ + return 1; + } else if (ctx->depth == 0) { + /* no iteration for the entry */ + return -1; + } else { + /* we just sent the entry root. iterate its children. */ + str_truncate(ctx->iter_entry_prefix, 0); + str_append(ctx->iter_entry_prefix, entry); + str_append_c(ctx->iter_entry_prefix, '/'); + + ctx->iter = imap_metadata_iter_init(ctx->trans, entry); + return 1; + } +} + +static void cmd_getmetadata_iter_deinit(struct imap_getmetadata_context *ctx) +{ + if (ctx->iter != NULL) + (void)imap_metadata_iter_deinit(&ctx->iter); + if (ctx->trans != NULL) + (void)imap_metadata_transaction_commit(&ctx->trans, NULL, NULL); + if (ctx->box != NULL) + mailbox_free(&ctx->box); + ctx->first_entry_sent = FALSE; + ctx->entry_idx = 0; +} + +static void cmd_getmetadata_deinit(struct imap_getmetadata_context *ctx) +{ + struct client_command_context *cmd = ctx->cmd; + + cmd_getmetadata_iter_deinit(ctx); + cmd->client->output_cmd_lock = NULL; + + if (ctx->list_iter != NULL && + mailbox_list_iter_deinit(&ctx->list_iter) < 0) + client_send_list_error(cmd, cmd->client->user->namespaces->list); + else if (ctx->last_error != 0) { + i_assert(str_len(ctx->last_error_str) > 0); + const char *tagline = + imap_get_error_string(cmd, str_c(ctx->last_error_str), + ctx->last_error); + client_send_tagline(cmd, tagline); + } else if (ctx->largest_seen_size != 0) { + client_send_tagline(cmd, t_strdup_printf( + "OK [METADATA LONGENTRIES %"PRIuUOFF_T"] " + "Getmetadata completed.", ctx->largest_seen_size)); + } else { + client_send_tagline(cmd, "OK Getmetadata completed."); + } +} + +static bool cmd_getmetadata_continue(struct client_command_context *cmd) +{ + struct imap_getmetadata_context *ctx = cmd->context; + const char *const *entries; + unsigned int count; + int ret; + + if (cmd->cancel) { + cmd_getmetadata_deinit(ctx); + return TRUE; + } + + if (ctx->cur_stream != NULL) { + if (!cmd_getmetadata_stream_continue(ctx)) + return FALSE; + i_stream_unref(&ctx->cur_stream); + } + + entries = array_get(&ctx->entries, &count); + for (; ctx->entry_idx < count; ctx->entry_idx++) { + do { + T_BEGIN { + ret = cmd_getmetadata_send_entry_tree(ctx, entries[ctx->entry_idx]); + } T_END; + if (ret == 0) + return FALSE; + } while (ret > 0); + } + if (ctx->first_entry_sent) + o_stream_nsend_str(cmd->client->output, ")\r\n"); + + if (str_len(ctx->delayed_errors) > 0) { + o_stream_nsend(cmd->client->output, + str_data(ctx->delayed_errors), + str_len(ctx->delayed_errors)); + str_truncate(ctx->delayed_errors, 0); + } + + cmd_getmetadata_iter_deinit(ctx); + if (ctx->list_iter != NULL) + return cmd_getmetadata_mailbox_iter_next(ctx); + cmd_getmetadata_deinit(ctx); + return TRUE; +} + +static bool +cmd_getmetadata_start(struct imap_getmetadata_context *ctx) +{ + struct client_command_context *cmd = ctx->cmd; + + if (ctx->depth > 0) + ctx->iter_entry_prefix = str_new(cmd->pool, 128); + imap_metadata_transaction_validated_only(ctx->trans, + !cmd->client->set->imap_metadata); + + if (!cmd_getmetadata_continue(cmd)) { + cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT; + cmd->func = cmd_getmetadata_continue; + return FALSE; + } + return TRUE; +} + +static bool +cmd_getmetadata_server(struct imap_getmetadata_context *ctx) +{ + ctx->trans = imap_metadata_transaction_begin_server(ctx->cmd->client->user); + return cmd_getmetadata_start(ctx); +} + +static int +cmd_getmetadata_try_mailbox(struct imap_getmetadata_context *ctx, + struct mail_namespace *ns, const char *mailbox) +{ + ctx->box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY); + event_add_str(ctx->cmd->global_event, "mailbox", + mailbox_get_vname(ctx->box)); + + enum mailbox_existence existence; + if (mailbox_exists(ctx->box, TRUE, &existence) < 0) { + return -1; + } else if (existence == MAILBOX_EXISTENCE_NONE) { + const char *err = t_strdup_printf(MAIL_ERRSTR_MAILBOX_NOT_FOUND, + mailbox_get_vname(ctx->box)); + mail_storage_set_error(ctx->box->storage, MAIL_ERROR_NOTFOUND, err); + return -1; + } + + ctx->trans = imap_metadata_transaction_begin(ctx->box); + return cmd_getmetadata_start(ctx) ? 1 : 0; +} + +static bool +cmd_getmetadata_mailbox(struct imap_getmetadata_context *ctx, + struct mail_namespace *ns, const char *mailbox) +{ + int ret; + + ret = cmd_getmetadata_try_mailbox(ctx, ns, mailbox); + if (ret < 0) { + client_send_box_error(ctx->cmd, ctx->box); + mailbox_free(&ctx->box); + } + return ret != 0; +} + +static bool +cmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx) +{ + const struct mailbox_info *info; + int ret; + + while ((info = mailbox_list_iter_next(ctx->list_iter)) != NULL) { + if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) != 0) + continue; + ret = cmd_getmetadata_try_mailbox(ctx, info->ns, info->vname); + if (ret > 0) { + /* we'll already recursively went through + all the mailboxes (FIXME: ugly and potentially + stack consuming) */ + return TRUE; + } else if (ret == 0) { + /* need to send more data later */ + return FALSE; + } + T_BEGIN { + client_send_line(ctx->cmd->client, t_strdup_printf( + "* NO Failed to open mailbox %s: %s", + info->vname, mailbox_get_last_error(ctx->box, NULL))); + } T_END; + mailbox_free(&ctx->box); + } + cmd_getmetadata_deinit(ctx); + return TRUE; +} + +bool cmd_getmetadata(struct client_command_context *cmd) +{ + struct imap_getmetadata_context *ctx; + struct mail_namespace *ns; + const struct imap_arg *args, *options, *entries; + const char *mailbox, *entry_name; + + if (!client_read_args(cmd, 0, 0, &args)) + return FALSE; + + ctx = p_new(cmd->pool, struct imap_getmetadata_context, 1); + ctx->cmd = cmd; + ctx->maxsize = (uint32_t)-1; + ctx->cmd->context = ctx; + ctx->delayed_errors = str_new(cmd->pool, 128); + ctx->last_error_str = str_new(cmd->pool, 128); + + if (imap_arg_get_list(&args[0], &options)) { + if (!cmd_getmetadata_parse_options(ctx, options)) + return TRUE; + args++; + } + if (!imap_arg_get_astring(&args[0], &mailbox)) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + if (!imap_arg_get_list(&args[1], &entries)) { + if (!imap_arg_get_astring(&args[1], &entry_name) || + !IMAP_ARG_IS_EOL(&args[2])) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + entries = args+1; + } + if (!imap_metadata_parse_entry_names(ctx, entries)) + return TRUE; + + if (mailbox[0] == '\0') { + /* server attribute */ + return cmd_getmetadata_server(ctx); + } else if (strchr(mailbox, '*') == NULL && + strchr(mailbox, '%') == NULL) { + /* mailbox attribute */ + ns = client_find_namespace(cmd, &mailbox); + if (ns == NULL) + return TRUE; + return cmd_getmetadata_mailbox(ctx, ns, mailbox); + } else { + /* wildcards in mailbox name. this isn't supported by RFC 5464, + but it was in the earlier drafts and is already used by + some software (Horde). */ + const char *patterns[2]; + patterns[0] = mailbox; patterns[1] = NULL; + + ctx->list_iter = + mailbox_list_iter_init_namespaces( + cmd->client->user->namespaces, + patterns, MAIL_NAMESPACE_TYPE_MASK_ALL, 0); + return cmd_getmetadata_mailbox_iter_next(ctx); + } +} |