/* Copyright (c) 2011-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "ioloop.h"
#include "str.h"
#include "imap-arg.h"
#include "imap-match.h"
#include "imap-utf7.h"
#include "mailbox-tree.h"
#include "mailbox-list-subscriptions.h"
#include "imapc-client.h"
#include "imapc-storage.h"
#include "imapc-list.h"

struct imapc_mailbox_list_iterate_context {
	struct mailbox_list_iterate_context ctx;
	struct mailbox_tree_context *tree;
	struct mailbox_node *ns_root;

	struct mailbox_tree_iterate_context *iter;
	struct mailbox_info info;
	string_t *special_use;
};

static struct {
	const char *str;
	enum mailbox_info_flags flag;
} imap_list_flags[] = {
	{ "\\NoSelect", MAILBOX_NOSELECT },
	{ "\\NonExistent", MAILBOX_NONEXISTENT },
	{ "\\NoInferiors", MAILBOX_NOINFERIORS },
	{ "\\Subscribed", MAILBOX_SUBSCRIBED },
	{ "\\All", MAILBOX_SPECIALUSE_ALL },
	{ "\\Archive", MAILBOX_SPECIALUSE_ARCHIVE },
	{ "\\Drafts", MAILBOX_SPECIALUSE_DRAFTS },
	{ "\\Flagged", MAILBOX_SPECIALUSE_FLAGGED },
	{ "\\Junk", MAILBOX_SPECIALUSE_JUNK },
	{ "\\Sent", MAILBOX_SPECIALUSE_SENT },
	{ "\\Trash", MAILBOX_SPECIALUSE_TRASH },
	{ "\\Important", MAILBOX_SPECIALUSE_IMPORTANT }
};

extern struct mailbox_list imapc_mailbox_list;

static void imapc_list_send_hierarcy_sep_lookup(struct imapc_mailbox_list *list);
static void imapc_untagged_list(const struct imapc_untagged_reply *reply,
				struct imapc_storage_client *client);
static void imapc_untagged_lsub(const struct imapc_untagged_reply *reply,
				struct imapc_storage_client *client);

static struct mailbox_list *imapc_list_alloc(void)
{
	struct imapc_mailbox_list *list;
	pool_t pool;

	pool = pool_alloconly_create("imapc mailbox list", 1024);
	list = p_new(pool, struct imapc_mailbox_list, 1);
	list->list = imapc_mailbox_list;
	list->list.pool = pool;
	/* separator is set lazily */
	list->mailboxes = mailbox_tree_init('\0');
	mailbox_tree_set_parents_nonexistent(list->mailboxes);
	return &list->list;
}

static int imapc_list_init(struct mailbox_list *_list, const char **error_r)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;

	list->set = mail_user_set_get_driver_settings(_list->ns->user->set_info,
						      _list->ns->user->set,
						      IMAPC_STORAGE_NAME);
	if (imapc_storage_client_create(_list->ns, list->set, _list->mail_set,
					&list->client, error_r) < 0)
		return -1;
	list->client->_list = list;

	imapc_storage_client_register_untagged(list->client, "LIST",
					       imapc_untagged_list);
	imapc_storage_client_register_untagged(list->client, "LSUB",
					       imapc_untagged_lsub);
	imapc_list_send_hierarcy_sep_lookup(list);
	return 0;
}

static void imapc_list_deinit(struct mailbox_list *_list)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;

	/* make sure all pending commands are aborted before anything is
	   deinitialized */
	if (list->client != NULL) {
		list->client->destroying = TRUE;
		imapc_client_disconnect(list->client->client);
		imapc_storage_client_unref(&list->client);
	}
	if (list->index_list != NULL)
		mailbox_list_destroy(&list->index_list);
	mailbox_tree_deinit(&list->mailboxes);
	if (list->tmp_subscriptions != NULL)
		mailbox_tree_deinit(&list->tmp_subscriptions);
	pool_unref(&list->list.pool);
}

static void
imapc_list_copy_error_from_reply(struct imapc_mailbox_list *list,
				 enum mail_error default_error,
				 const struct imapc_command_reply *reply)
{
	enum mail_error error;

	if (imap_resp_text_code_parse(reply->resp_text_key, &error)) {
		mailbox_list_set_error(&list->list, error,
				       reply->text_without_resp);
	} else {
		mailbox_list_set_error(&list->list, default_error,
				       reply->text_without_resp);
	}
}

static void imapc_list_simple_callback(const struct imapc_command_reply *reply,
				       void *context)
{
	struct imapc_simple_context *ctx = context;

	if (reply->state == IMAPC_COMMAND_STATE_OK)
		ctx->ret = 0;
	else if (reply->state == IMAPC_COMMAND_STATE_NO) {
		imapc_list_copy_error_from_reply(ctx->client->_list,
						 MAIL_ERROR_PARAMS, reply);
		ctx->ret = -1;
	} else if (ctx->client->auth_failed) {
		ctx->ret = -1;
	} else {
		mailbox_list_set_critical(&ctx->client->_list->list,
			"imapc: Command failed: %s", reply->text_full);
		ctx->ret = -1;
	}
	imapc_client_stop(ctx->client->client);
}

static bool
imap_list_flag_parse(const char *str, enum mailbox_info_flags *flag_r)
{
	unsigned int i;

	for (i = 0; i < N_ELEMENTS(imap_list_flags); i++) {
		if (strcasecmp(str, imap_list_flags[i].str) == 0) {
			*flag_r = imap_list_flags[i].flag;
			return TRUE;
		}
	}
	return FALSE;
}

static const char *
imapc_list_to_vname(struct imapc_mailbox_list *list, const char *imapc_name)
{
	const char *list_name;

	/* typically mailbox_list_escape_name() is used to escape vname into
	   a list name. but we want to convert remote IMAP name to a list name,
	   so we need to use the remote IMAP separator. */
	list_name = mailbox_list_escape_name_params(imapc_name, "", list->root_sep,
		mailbox_list_get_hierarchy_sep(&list->list),
		list->list.set.escape_char, "");
	/* list_name is now valid, so we can convert it to vname */
	return mailbox_list_get_vname(&list->list, list_name);
}

const char *imapc_list_to_remote(struct imapc_mailbox_list *list, const char *name)
{
	return mailbox_list_unescape_name_params(name, "", list->root_sep,
				mailbox_list_get_hierarchy_sep(&list->list),
				list->list.set.escape_char);
}

static struct mailbox_node *
imapc_list_update_tree(struct imapc_mailbox_list *list,
		       struct mailbox_tree_context *tree,
		       const struct imap_arg *args)
{
	struct mailbox_node *node;
	const struct imap_arg *flags;
	const char *name, *flag;
	enum mailbox_info_flags info_flag, info_flags = 0;
	bool created;

	if (!imap_arg_get_list(&args[0], &flags) ||
	    args[1].type == IMAP_ARG_EOL ||
	    !imap_arg_get_astring(&args[2], &name))
		return NULL;

	while (imap_arg_get_atom(flags, &flag)) {
		if (imap_list_flag_parse(flag, &info_flag))
			info_flags |= info_flag;
		flags++;
	}

	T_BEGIN {
		const char *vname = imapc_list_to_vname(list, name);

		if ((info_flags & MAILBOX_NONEXISTENT) != 0)
			node = mailbox_tree_lookup(tree, vname);
		else
			node = mailbox_tree_get(tree, vname, &created);
	} T_END;
	if (node != NULL)
		node->flags = info_flags;
	return node;
}

static void imapc_untagged_list(const struct imapc_untagged_reply *reply,
				struct imapc_storage_client *client)
{
	struct imapc_mailbox_list *list = client->_list;
	const struct imap_arg *args = reply->args;
	const char *sep, *name;

	if (list->root_sep == '\0') {
		/* we haven't asked for the separator yet.
		   lets see if this is the reply for its request. */
		if (args[0].type == IMAP_ARG_EOL ||
		    !imap_arg_get_nstring(&args[1], &sep) ||
		    !imap_arg_get_astring(&args[2], &name))
			return;

		/* we can't handle NIL separator yet */
		list->root_sep = sep == NULL ? '/' : sep[0];
		mailbox_tree_set_separator(list->mailboxes, list->root_sep);
	} else {
		(void)imapc_list_update_tree(list, list->mailboxes, args);
	}
}

static void imapc_untagged_lsub(const struct imapc_untagged_reply *reply,
				struct imapc_storage_client *client)
{
	struct imapc_mailbox_list *list = client->_list;
	const struct imap_arg *args = reply->args;
	struct mailbox_node *node;

	if (list->root_sep == '\0') {
		/* we haven't asked for the separator yet */
		return;
	}
	node = imapc_list_update_tree(list, list->tmp_subscriptions != NULL ?
				      list->tmp_subscriptions :
				      list->list.subscriptions, args);
	if (node != NULL) {
		if ((node->flags & MAILBOX_NOSELECT) == 0)
			node->flags |= MAILBOX_SUBSCRIBED;
		else {
			/* LSUB \Noselect means that the mailbox isn't
			   subscribed, but it has children that are */
			node->flags &= ~MAILBOX_NOSELECT;
		}
	}
}

static void imapc_list_sep_verify(struct imapc_mailbox_list *list)
{
	const char *imapc_list_prefix = list->set->imapc_list_prefix;

	if (list->root_sep == '\0') {
		mailbox_list_set_critical(&list->list,
			"imapc: LIST didn't return hierarchy separator");
	} else if (imapc_list_prefix[0] != '\0' &&
		   imapc_list_prefix[strlen(imapc_list_prefix)-1] == list->root_sep) {
		mailbox_list_set_critical(&list->list,
			"imapc_list_prefix must not end with hierarchy separator");
	}
}

static void imapc_storage_sep_callback(const struct imapc_command_reply *reply,
				       void *context)
{
	struct imapc_mailbox_list *list = context;

	list->root_sep_pending = FALSE;
	if (reply->state == IMAPC_COMMAND_STATE_OK)
		imapc_list_sep_verify(list);
	else if (reply->state == IMAPC_COMMAND_STATE_NO)
		imapc_list_copy_error_from_reply(list, MAIL_ERROR_PARAMS, reply);
	else if (list->client->auth_failed)
		;
	else if (!list->list.ns->user->deinitializing) {
		mailbox_list_set_critical(&list->list,
			"imapc: Command failed: %s", reply->text_full);
	}
	imapc_client_stop(list->client->client);
}

static void imapc_list_send_hierarcy_sep_lookup(struct imapc_mailbox_list *list)
{
	struct imapc_command *cmd;

	if (list->root_sep_pending)
		return;
	list->root_sep_pending = TRUE;

	cmd = imapc_client_cmd(list->client->client,
			       imapc_storage_sep_callback, list);
	imapc_command_send(cmd, "LIST \"\" \"\"");
}

int imapc_list_try_get_root_sep(struct imapc_mailbox_list *list, char *sep_r)
{
	if (list->root_sep == '\0') {
		if (list->client->auth_failed)
			return -1;
		imapc_list_send_hierarcy_sep_lookup(list);
		while (list->root_sep_pending)
			imapc_client_run(list->client->client);
		if (list->root_sep == '\0')
			return -1;
	}
	*sep_r = list->root_sep;
	return 0;
}

static char imapc_list_get_hierarchy_sep(struct mailbox_list *_list)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	char sep;

	if (imapc_list_try_get_root_sep(list, &sep) < 0) {
		/* we can't really fail here. just return a common separator
		   and keep failing all list commands until it succeeds. */
		return '/';
	}
	return sep;
}

static const char *
imapc_list_get_storage_name(struct mailbox_list *_list, const char *vname)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	const char *prefix = list->set->imapc_list_prefix;
	const char *storage_name;

	storage_name = mailbox_list_default_get_storage_name(_list, vname);
	if (*prefix != '\0' && strcasecmp(storage_name, "INBOX") != 0) {
		storage_name = storage_name[0] == '\0' ? prefix :
			t_strdup_printf("%s%c%s", prefix,
			mailbox_list_get_hierarchy_sep(_list),
			storage_name);
	}
	return storage_name;
}

static const char *
imapc_list_get_vname(struct mailbox_list *_list, const char *storage_name)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	const char *prefix = list->set->imapc_list_prefix;
	unsigned int prefix_len;

	if (*storage_name == '\0') {
		/* ACL plugin does these lookups */
	} else if (*prefix != '\0' && strcasecmp(storage_name, "INBOX") != 0) {
		prefix_len = strlen(prefix);
		i_assert(strncmp(prefix, storage_name, prefix_len) == 0);
		storage_name += prefix_len;
		if (storage_name[0] == '\0') {
			/* we're looking up the prefix itself */
		} else {
			i_assert(storage_name[0] ==
				 mailbox_list_get_hierarchy_sep(_list));
			storage_name++;
		}
	}
	return mailbox_list_default_get_vname(_list, storage_name);
}

static struct mailbox_list *imapc_list_get_fs(struct imapc_mailbox_list *list)
{
	struct mailbox_list_settings list_set;
	const char *error, *dir;

	dir = list->list.set.index_dir;
	if (dir == NULL)
		dir = list->list.set.root_dir;

	if (dir == NULL) {
		/* indexes disabled */
	} else if (list->index_list == NULL && !list->index_list_failed) {
		memset(&list_set, 0, sizeof(list_set));
		list_set.layout = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS;
		list_set.root_dir = dir;
		list_set.escape_char = IMAPC_LIST_ESCAPE_CHAR;
		list_set.broken_char = IMAPC_LIST_BROKEN_CHAR;
		list_set.mailbox_dir_name = "";
		list_set.maildir_name = "";

		if (mailbox_list_create(list_set.layout, list->list.ns,
					&list_set, MAILBOX_LIST_FLAG_SECONDARY,
					&list->index_list, &error) < 0) {
			i_error("imapc: Couldn't create %s mailbox list: %s",
				list_set.layout, error);
			list->index_list_failed = TRUE;
		}
	}
	return list->index_list;
}

static const char *
imapc_list_get_fs_name(struct imapc_mailbox_list *list, const char *name)
{
	struct mailbox_list *fs_list = imapc_list_get_fs(list);
	struct mail_namespace *ns = list->list.ns;
	const char *vname;
	char ns_sep = mail_namespace_get_sep(ns);

	if (name == NULL)
		return NULL;

	vname = mailbox_list_get_vname(&list->list, name);
	if (list->set->imapc_list_prefix[0] != '\0') {
		/* put back the prefix, so it gets included in the filesystem. */
		unsigned int vname_len = strlen(vname);

		if (ns->prefix_len > 0) {
			/* skip over the namespace prefix */
			i_assert(strncmp(vname, ns->prefix, ns->prefix_len-1) == 0);
			if (vname_len == ns->prefix_len-1)
				vname = "";
			else {
				i_assert(vname[ns->prefix_len-1] == ns_sep);
				vname += ns->prefix_len;
			}
		}
		if (vname[0] == '\0') {
			vname = t_strconcat(ns->prefix,
				list->set->imapc_list_prefix, NULL);
		} else {
			vname = t_strdup_printf("%s%s%c%s", ns->prefix,
						list->set->imapc_list_prefix,
						ns_sep, vname);
		}
	}
	return mailbox_list_get_storage_name(fs_list, vname);
}

static int
imapc_list_get_path(struct mailbox_list *_list, const char *name,
		    enum mailbox_list_path_type type, const char **path_r)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	struct mailbox_list *fs_list = imapc_list_get_fs(list);
	const char *fs_name;

	if (fs_list != NULL) {
		fs_name = imapc_list_get_fs_name(list, name);
		return mailbox_list_get_path(fs_list, fs_name, type, path_r);
	} else {
		*path_r = NULL;
		return 0;
	}
}

static const char *
imapc_list_get_temp_prefix(struct mailbox_list *_list, bool global)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	struct mailbox_list *fs_list = imapc_list_get_fs(list);

	if (fs_list != NULL) {
		return global ?
			mailbox_list_get_global_temp_prefix(fs_list) :
			mailbox_list_get_temp_prefix(fs_list);
	} else {
		i_panic("imapc: Can't return a temp prefix for '%s'",
			_list->ns->prefix);
		return NULL;
	}
}

static const char *
imapc_list_join_refpattern(struct mailbox_list *list ATTR_UNUSED,
			   const char *ref, const char *pattern)
{
	return t_strconcat(ref, pattern, NULL);
}

static struct imapc_command *
imapc_list_simple_context_init(struct imapc_simple_context *ctx,
			       struct imapc_mailbox_list *list)
{
	imapc_simple_context_init(ctx, list->client);
	return imapc_client_cmd(list->client->client,
				imapc_list_simple_callback, ctx);
}

static void imapc_list_delete_unused_indexes(struct imapc_mailbox_list *list)
{
	struct mailbox_list *fs_list = imapc_list_get_fs(list);
	struct mailbox_list_iterate_context *iter;
	const struct mailbox_info *info;
	const char *imapc_list_prefix = list->set->imapc_list_prefix;
	unsigned int imapc_list_prefix_len = strlen(imapc_list_prefix);
	const char *fs_name, *vname;

	if (fs_list == NULL)
		return;

	iter = mailbox_list_iter_init(fs_list, "*",
				      MAILBOX_LIST_ITER_RAW_LIST |
				      MAILBOX_LIST_ITER_NO_AUTO_BOXES |
				      MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
	while ((info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
		vname = info->vname;
		if (imapc_list_prefix_len > 0 &&
		    strcasecmp(vname, "INBOX") != 0) {
			/* skip over the namespace prefix */
			i_assert(strncmp(vname, fs_list->ns->prefix,
					 fs_list->ns->prefix_len) == 0);
			vname += fs_list->ns->prefix_len;
			/* skip over the imapc list prefix */
			i_assert(strncmp(vname, imapc_list_prefix,
					 imapc_list_prefix_len) == 0);
			vname += imapc_list_prefix_len;
			if (vname[0] != '\0') {
				i_assert(vname[0] == mail_namespace_get_sep(fs_list->ns));
				vname++;
			}
			/* put back the namespace prefix */
			if (fs_list->ns->prefix_len > 0) {
				vname = t_strconcat(fs_list->ns->prefix,
						    vname, NULL);
			}
		}
		if (mailbox_tree_lookup(list->mailboxes, vname) == NULL) {
			fs_name = mailbox_list_get_storage_name(fs_list,
								info->vname);
			(void)fs_list->v.delete_mailbox(fs_list, fs_name);
		}
	} T_END;
	(void)mailbox_list_iter_deinit(&iter);
}

static int imapc_list_refresh(struct imapc_mailbox_list *list)
{
	struct imapc_command *cmd;
	struct imapc_simple_context ctx;
	struct mailbox_node *node;
	const char *pattern;

	if (list->refreshed_mailboxes)
		return 0;

	if (*list->set->imapc_list_prefix == '\0')
		pattern = "*";
	else {
		/* list "prefix*" instead of "prefix.*". this may return a bit
		   more than we want, but we're also interested in the flags
		   of the prefix itself. */
		pattern = t_strdup_printf("%s*", list->set->imapc_list_prefix);
	}

	cmd = imapc_list_simple_context_init(&ctx, list);
	imapc_command_sendf(cmd, "LIST \"\" %s", pattern);
	mailbox_tree_deinit(&list->mailboxes);
	list->mailboxes = mailbox_tree_init(mail_namespace_get_sep(list->list.ns));
	mailbox_tree_set_parents_nonexistent(list->mailboxes);
	imapc_simple_run(&ctx);

	if ((list->list.ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
		/* INBOX always exists in IMAP server. since this namespace is
		   marked with inbox=yes, show the INBOX even if
		   imapc_list_prefix doesn't match it */
		bool created;
		node = mailbox_tree_get(list->mailboxes, "INBOX", &created);
		if (*list->set->imapc_list_prefix != '\0') {
			/* this listing didn't include the INBOX itself, but
			   might have included its children. make sure there
			   aren't any extra flags in it (especially
			   \NonExistent) */
			node->flags &= MAILBOX_CHILDREN;
		}
	}

	if (ctx.ret == 0) {
		list->refreshed_mailboxes = TRUE;
		list->refreshed_mailboxes_recently = TRUE;
		list->last_refreshed_mailboxes = ioloop_time;
		imapc_list_delete_unused_indexes(list);
	}
	return ctx.ret;
}

static void
imapc_list_build_match_tree(struct imapc_mailbox_list_iterate_context *ctx)
{
	struct imapc_mailbox_list *list =
		(struct imapc_mailbox_list *)ctx->ctx.list;
	struct mailbox_list_iter_update_context update_ctx;
	struct mailbox_tree_iterate_context *iter;
	struct mailbox_node *node;
	const char *name;

	memset(&update_ctx, 0, sizeof(update_ctx));
	update_ctx.iter_ctx = &ctx->ctx;
	update_ctx.tree_ctx = ctx->tree;
	update_ctx.glob = ctx->ctx.glob;
	update_ctx.match_parents = TRUE;

	iter = mailbox_tree_iterate_init(list->mailboxes, NULL, 0);
	while ((node = mailbox_tree_iterate_next(iter, &name)) != NULL) {
		update_ctx.leaf_flags = node->flags;
		mailbox_list_iter_update(&update_ctx, name);
	}
	mailbox_tree_iterate_deinit(&iter);
}

static struct mailbox_list_iterate_context *
imapc_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
		     enum mailbox_list_iter_flags flags)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	struct mailbox_list_iterate_context *_ctx;
	struct imapc_mailbox_list_iterate_context *ctx;
	pool_t pool;
	const char *ns_root_name;
	char ns_sep;
	int ret = 0;

	if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 ||
	    (flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0)
		ret = imapc_list_refresh(list);

	list->iter_count++;

	if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
		/* we're listing only subscriptions. just use the cached
		   subscriptions list. */
		_ctx = mailbox_list_subscriptions_iter_init(_list, patterns,
							    flags);
		if (ret < 0)
			_ctx->failed = TRUE;
		return _ctx;
	}

	ns_sep = mail_namespace_get_sep(_list->ns);

	pool = pool_alloconly_create("mailbox list imapc iter", 1024);
	ctx = p_new(pool, struct imapc_mailbox_list_iterate_context, 1);
	ctx->ctx.pool = pool;
	ctx->ctx.list = _list;
	ctx->ctx.flags = flags;
	ctx->ctx.glob = imap_match_init_multiple(pool, patterns, FALSE, ns_sep);
	array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);

	ctx->info.ns = _list->ns;

	ctx->tree = mailbox_tree_init(ns_sep);
	mailbox_tree_set_parents_nonexistent(ctx->tree);
	imapc_list_build_match_tree(ctx);

	if (list->list.ns->prefix_len > 0) {
		ns_root_name = t_strndup(_list->ns->prefix,
					 _list->ns->prefix_len - 1);
		ctx->ns_root = mailbox_tree_lookup(ctx->tree, ns_root_name);
	}

	ctx->iter = mailbox_tree_iterate_init(ctx->tree, NULL, 0);
	if (ret < 0)
		ctx->ctx.failed = TRUE;
	return &ctx->ctx;
}

static void
imapc_list_write_special_use(struct imapc_mailbox_list_iterate_context *ctx,
			     struct mailbox_node *node)
{
	unsigned int i;

	if (ctx->special_use == NULL)
		ctx->special_use = str_new(ctx->ctx.pool, 64);
	str_truncate(ctx->special_use, 0);

	for (i = 0; i < N_ELEMENTS(imap_list_flags); i++) {
		if ((node->flags & imap_list_flags[i].flag) != 0 &&
		    (node->flags & MAILBOX_SPECIALUSE_MASK) != 0) {
			str_append(ctx->special_use, imap_list_flags[i].str);
			str_append_c(ctx->special_use, ' ');
		}
	}

	if (str_len(ctx->special_use) > 0) {
		str_truncate(ctx->special_use, str_len(ctx->special_use) - 1);
		ctx->info.special_use = str_c(ctx->special_use);
	} else {
		ctx->info.special_use = NULL;
	}
}

static const struct mailbox_info *
imapc_list_iter_next(struct mailbox_list_iterate_context *_ctx)
{
	struct imapc_mailbox_list_iterate_context *ctx =
		(struct imapc_mailbox_list_iterate_context *)_ctx;
	struct imapc_mailbox_list *list =
		(struct imapc_mailbox_list *)_ctx->list;
	struct mailbox_node *node;
	const char *vname;

	if (_ctx->failed)
		return NULL;

	if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
		return mailbox_list_subscriptions_iter_next(_ctx);

	do {
		node = mailbox_tree_iterate_next(ctx->iter, &vname);
		if (node == NULL)
			return NULL;
	} while ((node->flags & MAILBOX_MATCHED) == 0);

	if (ctx->info.ns->prefix_len > 0 &&
	    strncmp(vname, ctx->info.ns->prefix, ctx->info.ns->prefix_len-1) == 0 &&
	    vname[ctx->info.ns->prefix_len] == '\0' &&
	    list->set->imapc_list_prefix[0] == '\0') {
		/* don't return "" name */
		return imapc_list_iter_next(_ctx);
	}

	ctx->info.vname = vname;
	ctx->info.flags = node->flags;
	if ((_ctx->list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
		/* we're iterating the INBOX namespace. pass through the
		   SPECIAL-USE flags if they exist. */
		imapc_list_write_special_use(ctx, node);
	} else {
		ctx->info.special_use = NULL;
	}
	return &ctx->info;
}

static int imapc_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
{
	struct imapc_mailbox_list_iterate_context *ctx =
		(struct imapc_mailbox_list_iterate_context *)_ctx;
	struct imapc_mailbox_list *list =
		(struct imapc_mailbox_list *)_ctx->list;
	int ret = _ctx->failed ? -1 : 0;

	i_assert(list->iter_count > 0);

	if (--list->iter_count == 0) {
		list->refreshed_mailboxes = FALSE;
		list->refreshed_subscriptions = FALSE;
	}

	if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
		return mailbox_list_subscriptions_iter_deinit(_ctx);

	mailbox_tree_iterate_deinit(&ctx->iter);
	mailbox_tree_deinit(&ctx->tree);
	pool_unref(&_ctx->pool);
	return ret;
}

static int
imapc_list_subscriptions_refresh(struct mailbox_list *_src_list,
				 struct mailbox_list *dest_list)
{
	struct imapc_mailbox_list *src_list =
		(struct imapc_mailbox_list *)_src_list;
	struct imapc_simple_context ctx;
	struct imapc_command *cmd;
	const char *pattern;
	char dest_sep = mail_namespace_get_sep(dest_list->ns);

	i_assert(src_list->tmp_subscriptions == NULL);

	if (src_list->refreshed_subscriptions) {
		if (dest_list->subscriptions == NULL)
			dest_list->subscriptions = mailbox_tree_init(dest_sep);
		return 0;
	}

	src_list->tmp_subscriptions =
		mailbox_tree_init(mail_namespace_get_sep(_src_list->ns));

	cmd = imapc_list_simple_context_init(&ctx, src_list);
	if (*src_list->set->imapc_list_prefix == '\0')
		pattern = "*";
	else
		pattern = t_strdup_printf("%s*", src_list->set->imapc_list_prefix);
	imapc_command_sendf(cmd, "LSUB \"\" %s", pattern);
	imapc_simple_run(&ctx);

	/* replace subscriptions tree in destination */
	if (dest_list->subscriptions != NULL)
		mailbox_tree_deinit(&dest_list->subscriptions);
	dest_list->subscriptions = src_list->tmp_subscriptions;
	src_list->tmp_subscriptions = NULL;
	mailbox_tree_set_separator(dest_list->subscriptions, dest_sep);

	src_list->refreshed_subscriptions = TRUE;
	return 0;
}

static int imapc_list_set_subscribed(struct mailbox_list *_list,
				     const char *name, bool set)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	struct imapc_command *cmd;
	struct imapc_simple_context ctx;

	cmd = imapc_list_simple_context_init(&ctx, list);
	imapc_command_sendf(cmd, set ? "SUBSCRIBE %s" : "UNSUBSCRIBE %s",
			    imapc_list_to_remote(list, name));
	imapc_simple_run(&ctx);
	return ctx.ret;
}

static int
imapc_list_delete_mailbox(struct mailbox_list *_list, const char *name)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	struct mailbox_list *fs_list = imapc_list_get_fs(list);
	enum imapc_capability capa;
	struct imapc_command *cmd;
	struct imapc_simple_context ctx;

	capa = imapc_client_get_capabilities(list->client->client);

	cmd = imapc_list_simple_context_init(&ctx, list);
	if (!imapc_command_connection_is_selected(cmd))
		imapc_command_abort(&cmd);
	else {
		imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_SELECT);
		if ((capa & IMAPC_CAPABILITY_UNSELECT) != 0)
			imapc_command_sendf(cmd, "UNSELECT");
		else
			imapc_command_sendf(cmd, "SELECT \"~~~\"");
		imapc_simple_run(&ctx);
	}

	cmd = imapc_list_simple_context_init(&ctx, list);
	imapc_command_sendf(cmd, "DELETE %s", imapc_list_to_remote(list, name));
	imapc_simple_run(&ctx);

	if (fs_list != NULL && ctx.ret == 0) {
		name = imapc_list_get_fs_name(list, name);
		(void)fs_list->v.delete_mailbox(fs_list, name);
	}
	return ctx.ret;
}

static int
imapc_list_delete_dir(struct mailbox_list *_list, const char *name)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	struct mailbox_list *fs_list = imapc_list_get_fs(list);

	if (fs_list != NULL) {
		name = imapc_list_get_fs_name(list, name);
		(void)mailbox_list_delete_dir(fs_list, name);
	}
	return 0;
}

static int
imapc_list_delete_symlink(struct mailbox_list *list,
			  const char *name ATTR_UNUSED)
{
	mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Not supported");
	return -1;
}

static int
imapc_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
			  struct mailbox_list *newlist, const char *newname)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)oldlist;
	struct mailbox_list *fs_list = imapc_list_get_fs(list);
	struct imapc_command *cmd;
	struct imapc_simple_context ctx;

	if (oldlist != newlist) {
		mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
			"Can't rename mailboxes across storages.");
		return -1;
	}

	cmd = imapc_list_simple_context_init(&ctx, list);
	imapc_command_sendf(cmd, "RENAME %s %s",
			    imapc_list_to_remote(list, oldname),
			    imapc_list_to_remote(list, newname));
	imapc_simple_run(&ctx);
	if (ctx.ret == 0 && fs_list != NULL && oldlist == newlist) {
		oldname = imapc_list_get_fs_name(list, oldname);
		newname = imapc_list_get_fs_name(list, newname);
		(void)fs_list->v.rename_mailbox(fs_list, oldname,
						fs_list, newname);
	}
	return ctx.ret;
}

int imapc_list_get_mailbox_flags(struct mailbox_list *_list, const char *name,
				 enum mailbox_info_flags *flags_r)
{
	struct imapc_mailbox_list *list = (struct imapc_mailbox_list *)_list;
	struct mailbox_node *node;
	const char *vname;

	vname = mailbox_list_get_vname(_list, name);
	if (!list->refreshed_mailboxes_recently) {
		if (imapc_list_refresh(list) < 0)
			return -1;
		i_assert(list->refreshed_mailboxes_recently);
	}

	node = mailbox_tree_lookup(list->mailboxes, vname);
	if (node == NULL)
		*flags_r = MAILBOX_NONEXISTENT;
	else
		*flags_r = node->flags;
	return 0;
}

struct mailbox_list imapc_mailbox_list = {
	.name = MAILBOX_LIST_NAME_IMAPC,
	.props = MAILBOX_LIST_PROP_NO_ROOT | MAILBOX_LIST_PROP_AUTOCREATE_DIRS,
	.mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,

	{
		imapc_list_alloc,
		imapc_list_init,
		imapc_list_deinit,
		NULL,
		imapc_list_get_hierarchy_sep,
		imapc_list_get_vname,
		imapc_list_get_storage_name,
		imapc_list_get_path,
		imapc_list_get_temp_prefix,
		imapc_list_join_refpattern,
		imapc_list_iter_init,
		imapc_list_iter_next,
		imapc_list_iter_deinit,
		NULL,
		NULL,
		imapc_list_subscriptions_refresh,
		imapc_list_set_subscribed,
		imapc_list_delete_mailbox,
		imapc_list_delete_dir,
		imapc_list_delete_symlink,
		imapc_list_rename_mailbox,
		NULL, NULL, NULL, NULL
	}
};
