bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen struct mailbox_list_iterate_context *list_iter;
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Boschcmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainencmd_getmetadata_parse_options(struct imap_getmetadata_context *ctx,
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen if (imap_arg_atom_equals(options, "MAXSIZE")) {
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen "Invalid value for MAXSIZE option");
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen } else if (imap_arg_atom_equals(options, "DEPTH")) {
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen "Invalid value for DEPTH option");
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen "Invalid value for DEPTH option");
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen client_send_command_error(ctx->cmd, "Unknown option");
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainenimap_metadata_parse_entry_names(struct imap_getmetadata_context *ctx,
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen p_array_init(&ctx->entries, ctx->cmd->pool, 4);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen for (; !IMAP_ARG_IS_EOL(entries); entries++) {
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen client_send_command_error(ctx->cmd, "Entry isn't astring");
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch if (!imap_metadata_verify_entry_name(value, &error)) {
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* names are case-insensitive so we'll always lowercase them */
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen value = p_strdup(ctx->cmd->pool, t_str_lcase(value));
a5c1051a846db2aa91473ecced02d8d91f249357Timo Sirainenmetadata_add_entry(struct imap_getmetadata_context *ctx, const char *entry)
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch /* server metadata reply */
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch if (imap_utf8_to_utf7(mailbox_get_vname(ctx->box), mailbox_mutf7) < 0)
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch imap_append_astring(str, str_c(mailbox_mutf7));
a5c1051a846db2aa91473ecced02d8d91f249357Timo Sirainen /* nothing can be sent until untagged METADATA is finished */
a5c1051a846db2aa91473ecced02d8d91f249357Timo Sirainencmd_getmetadata_send_nil_reply(struct imap_getmetadata_context *ctx,
a5c1051a846db2aa91473ecced02d8d91f249357Timo Sirainen /* client requested a specific entry that didn't exist.
a5c1051a846db2aa91473ecced02d8d91f249357Timo Sirainen we must return it as NIL. */
6d0e6c57cf45ad517748cfbb5f3aa3b53e42e3d0Martti Rannanjärvi o_stream_nsend(ctx->cmd->client->output, str_data(str), str_len(str));
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainenstatic void cmd_getmetadata_send_entry(struct imap_getmetadata_context *ctx,
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch if (imap_metadata_get_stream(ctx->trans, entry, &value) < 0) {
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch error_string = imap_metadata_transaction_get_last_error(
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen if (error != MAIL_ERROR_NOTFOUND && error != MAIL_ERROR_PERM) {
a1a79357a8d8e18806fd9888559939b7f60c4426Timo Sirainen str_printfa(ctx->delayed_errors, "* NO %s\r\n",
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen if (i_stream_get_size(value.value_stream, TRUE, &value_len) < 0) {
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen i_error("GETMETADATA %s: i_stream_get_size(%s) failed: %s", entry,
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* skip nonexistent entries */
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* value length is larger than specified MAXSIZE,
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen skip this entry */
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen str_printfa(str, " {%"PRIuUOFF_T"}\r\n%s", value_len, value.value);
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch o_stream_nsend(client->output, str_data(str), str_len(str));
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", value_len);
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch o_stream_nsend(client->output, str_data(str), str_len(str));
0e0fe9bed9a2be8760acf93abc6e16758195ef38Timo Sirainen ctx->cur_stream = i_stream_create_sized(value.value_stream, value_len);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainencmd_getmetadata_stream_continue(struct imap_getmetadata_context *ctx)
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen o_stream_set_max_buffer_size(ctx->cmd->client->output, 0);
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen res = o_stream_send_istream(ctx->cmd->client->output, ctx->cur_stream);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen o_stream_set_max_buffer_size(ctx->cmd->client->output, (size_t)-1);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen "Internal GETMETADATA failure");
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
378e6cb162b355d6f103526505bc00b9a78962e7Timo Sirainen /* client disconnected */
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Boschcmd_getmetadata_send_entry_tree(struct imap_getmetadata_context *ctx,
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch if (o_stream_get_buffer_used_size(client->output) >=
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch o_stream_set_flush_pending(client->output, TRUE);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* DEPTH iteration */
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch subentry = imap_metadata_iter_next(ctx->iter);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* iteration finished, get to the next entry */
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch if (imap_metadata_iter_deinit(&ctx->iter) < 0) {
a1a79357a8d8e18806fd9888559939b7f60c4426Timo Sirainen str_printfa(ctx->delayed_errors, "* NO %s\r\n",
a1a79357a8d8e18806fd9888559939b7f60c4426Timo Sirainen imap_metadata_transaction_get_last_error(ctx->trans, &error));
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch } while (ctx->depth == 1 && strchr(subentry, '/') != NULL);
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch entry = t_strconcat(str_c(ctx->iter_entry_prefix), subentry, NULL);
a5c1051a846db2aa91473ecced02d8d91f249357Timo Sirainen cmd_getmetadata_send_entry(ctx, entry, ctx->iter == NULL);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* already iterating the entry */
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* no iteration for the entry */
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* we just sent the entry root. iterate its children. */
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch ctx->iter = imap_metadata_iter_init(ctx->trans, entry);
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Boschstatic void cmd_getmetadata_iter_deinit(struct imap_getmetadata_context *ctx)
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch (void)imap_metadata_transaction_commit(&ctx->trans, NULL, NULL);
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainenstatic void cmd_getmetadata_deinit(struct imap_getmetadata_context *ctx)
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen struct client_command_context *cmd = ctx->cmd;
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen mailbox_list_iter_deinit(&ctx->list_iter) < 0)
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen client_send_list_error(cmd, cmd->client->user->namespaces->list);
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen client_send_tagline(cmd, "NO Getmetadata failed to send some entries");
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen "Getmetadata completed.", ctx->largest_seen_size));
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen client_send_tagline(cmd, "OK Getmetadata completed.");
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainenstatic bool cmd_getmetadata_continue(struct client_command_context *cmd)
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen struct imap_getmetadata_context *ctx = cmd->context;
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen for (; ctx->entry_idx < count; ctx->entry_idx++) {
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen ret = cmd_getmetadata_send_entry_tree(ctx, entries[ctx->entry_idx]);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen } while (ret > 0);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen o_stream_nsend_str(cmd->client->output, ")\r\n");
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch return cmd_getmetadata_mailbox_iter_next(ctx);
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Boschcmd_getmetadata_start(struct imap_getmetadata_context *ctx)
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen struct client_command_context *cmd = ctx->cmd;
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen ctx->iter_entry_prefix = str_new(cmd->pool, 128);
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Boschcmd_getmetadata_server(struct imap_getmetadata_context *ctx)
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch ctx->trans = imap_metadata_transaction_begin_server(ctx->cmd->client->user);
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Boschcmd_getmetadata_try_mailbox(struct imap_getmetadata_context *ctx,
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch struct mail_namespace *ns, const char *mailbox)
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch ctx->box = mailbox_alloc(ns->list, mailbox, MAILBOX_FLAG_READONLY);
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch ctx->trans = imap_metadata_transaction_begin(ctx->box);
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainencmd_getmetadata_mailbox(struct imap_getmetadata_context *ctx,
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen struct mail_namespace *ns, const char *mailbox)
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen ret = cmd_getmetadata_try_mailbox(ctx, ns, mailbox);
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen return ret != 0;
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Boschcmd_getmetadata_mailbox_iter_next(struct imap_getmetadata_context *ctx)
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen while ((info = mailbox_list_iter_next(ctx->list_iter)) != NULL) {
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) != 0)
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen ret = cmd_getmetadata_try_mailbox(ctx, info->ns, info->vname);
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen /* we'll already recursively went through
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen all the mailboxes (FIXME: ugly and potentially
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen stack consuming) */
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen } else if (ret == 0) {
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen /* need to send more data later */
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen client_send_line(ctx->cmd->client, t_strdup_printf(
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen "* NO Failed to open mailbox %s: %s",
730f8250fe4f840e2f708b9648e90e0069772088Timo Sirainen info->vname, mailbox_get_last_error(ctx->box, NULL)));
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainenbool cmd_getmetadata(struct client_command_context *cmd)
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen const struct imap_arg *args, *options, *entries;
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen client_send_command_error(cmd, "METADATA disabled.");
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen ctx = p_new(cmd->pool, struct imap_getmetadata_context, 1);
a1a79357a8d8e18806fd9888559939b7f60c4426Timo Sirainen ctx->delayed_errors = str_new(cmd->pool, 128);
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen if (!cmd_getmetadata_parse_options(ctx, options))
77b4e728245a1905b1cce68fce9e9b7adf7efc2cTimo Sirainen if (!imap_arg_get_astring(&args[0], &mailbox)) {
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen client_send_command_error(cmd, "Invalid arguments.");
77b4e728245a1905b1cce68fce9e9b7adf7efc2cTimo Sirainen if (!imap_arg_get_astring(&args[1], &entry_name) ||
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen client_send_command_error(cmd, "Invalid arguments.");
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen if (!imap_metadata_parse_entry_names(ctx, entries))
02c75e04c6ff80726bb59e3ea34a7995ad1f6f7cTimo Sirainen /* server attribute */
8ccdf195768afdfbc32088d7be77dfca7dddd829Stephan Bosch /* mailbox attribute */
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen return cmd_getmetadata_mailbox(ctx, ns, mailbox);
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen /* wildcards in mailbox name. this isn't supported by RFC 5464,
b6709d9c413338d32187fb0cf5ba32abaf16c20eTimo Sirainen but it was in the earlier drafts and is already used by
08b0192ede9e06c6e56363e96c1eee9c913c1a29Timo Sirainen some software (Horde). */