view src/imap/cmd-list.c @ 1329:ae229b7acb4c HEAD

Mailbox names are now sent through imap-quoter instead of just escaping it. This means that mailbox names that would require escapes are instead sent as literals now.
author Timo Sirainen <tss@iki.fi>
date Wed, 02 Apr 2003 05:05:38 +0300
parents f0ae8979b0ab
children 4d1c65eded2c
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "str.h"
#include "strescape.h"
#include "imap-quote.h"
#include "imap-match.h"
#include "commands.h"

struct list_node {
	struct list_node *next;
	struct list_node *children;

	char *name; /* escaped */
	enum mailbox_flags flags;
};

struct list_context {
	pool_t pool;
	struct list_node *nodes;
	struct mail_storage *storage;
};

struct list_send_context {
	struct client *client;
	const char *response_name;
	const char *sep;
	char sep_chr;
	struct imap_match_glob *glob;
	int listext, no_placeholder;
};

static const char *mailbox_flags2str(enum mailbox_flags flags,
				     int listext, int no_placeholder)
{
	const char *str;

	if (flags & MAILBOX_PLACEHOLDER) {
		if ((flags & ~MAILBOX_CHILDREN) == MAILBOX_PLACEHOLDER) {
			if (!listext || no_placeholder)
				flags = MAILBOX_NOSELECT;
		} else {
			/* it was at one point, but then we got better specs */
			flags &= ~MAILBOX_PLACEHOLDER;
		}
		flags |= MAILBOX_CHILDREN;
	}
	if ((flags & MAILBOX_NONEXISTENT) != 0 && !listext)
		flags |= MAILBOX_NOSELECT;

	str = t_strconcat((flags & MAILBOX_NOSELECT) ? " \\Noselect" : "",
			  (flags & MAILBOX_NONEXISTENT) ? " \\NonExistent" : "",
			  (flags & MAILBOX_PLACEHOLDER) ? " \\PlaceHolder" : "",
			  (flags & MAILBOX_CHILDREN) ? " \\Children" : "",
			  (flags & MAILBOX_NOCHILDREN) ? " \\NoChildren" : "",
			  (flags & MAILBOX_NOINFERIORS) ? " \\NoInferiors" : "",
			  (flags & MAILBOX_MARKED) ? " \\Marked" : "",
			  (flags & MAILBOX_UNMARKED) ? " \\UnMarked" : "",
			  NULL);

	return *str == '\0' ? "" : str+1;
}

static void list_node_update(pool_t pool, struct list_node **node,
			     const char *path, char separator,
			     enum mailbox_flags flags)
{
	const char *name, *parent;

	parent = NULL;

	for (name = path;; path++) {
		if (*path != separator && *path != '\0')
			continue;

		t_push();

		name = t_strdup_until(name, path);

		/* find the node */
		while (*node != NULL) {
			if (strcmp((*node)->name, name) == 0)
				break;

			node = &(*node)->next;
		}

		if (*node == NULL) {
			/* not found, create it */
			*node = p_new(pool, struct list_node, 1);
			(*node)->name = p_strdup(pool, name);
			(*node)->flags = *path == '\0' ? flags :
				MAILBOX_PLACEHOLDER;
		} else {
			if (*path == '\0') {
				if (((*node)->flags & MAILBOX_NOSELECT) != 0 &&
				    (flags & MAILBOX_NOSELECT) == 0) {
					/* overrides previous flag */
					(*node)->flags &= ~MAILBOX_NOSELECT;
				}

				(*node)->flags |= flags;
			}
		}

		t_pop();

		if (*path == '\0')
			break;

		name = path+1;
		parent = (*node)->name;
		node = &(*node)->children;
	}
}

static void list_send(struct list_send_context *ctx, struct list_node *node,
		      const char *path)
{
	const char *name, *send_name, *flagstr;
	enum imap_match_result match;
	string_t *str;

	for (; node != NULL; node = node->next) {
		t_push();

		/* Send INBOX always uppercased */
		if (path != NULL) {
			name = t_strdup_printf("%s%c%s", path, ctx->sep_chr,
					       node->name);
		} else if (strcasecmp(node->name, "INBOX") == 0)
			name = "INBOX";
		else
			name = node->name;
		send_name = name;

		if ((node->flags & MAILBOX_PLACEHOLDER) == 0 &&
		    (node->flags & MAILBOX_NOSELECT) == 0)
			match = IMAP_MATCH_YES;
		else {
			/* make sure the placeholder matches. */
			const char *buf;

			buf = str_unescape(t_strdup_noconst(name));
			match = imap_match(ctx->glob, buf);
			/* FIXME: IMAP spec says this should be done, but
			   a) this is broken, we shouldn't give \NoSelect for
			      this folder if it actually works.
			   b) at least mozilla's subscriptions list breaks if
			      this is sent
			   c) cyrus and courier doesn't do this either..

			if (match == IMAP_MATCH_CHILDREN) {
				send_name = t_strdup_printf("%s%c", name,
							    ctx->sep);
				buf = str_unescape(t_strdup_noconst(send_name));
				match = imap_match(ctx->glob, buf);
			}*/
		}

		if (match == IMAP_MATCH_YES) {
			/* node->name should already be escaped */
			flagstr = mailbox_flags2str(node->flags, ctx->listext,
						    ctx->no_placeholder);
			t_push();
			str = t_str_new(256);
			str_printfa(str, "* %s (%s) \"%s\" ",
				    ctx->response_name, flagstr, ctx->sep);
			imap_quote_append_string(str, send_name);
			client_send_line(ctx->client, str_c(str));
			t_pop();
		}

		if (node->children != NULL)
			list_send(ctx, node->children,  name);

		t_pop();
	}
}

static void list_and_sort(struct client *client,
			  struct mailbox_list_context *ctx,
			  const char *response_name, const char *mask,
			  const char *sep, char sep_chr,
			  enum mailbox_list_flags list_flags, int listext)
{
	struct mailbox_list *list;
	struct list_node *nodes;
	struct list_send_context send_ctx;
	pool_t pool;

	pool = pool_alloconly_create("list_mailboxes", 10240);
	nodes = NULL;

	while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
		list_node_update(pool, &nodes, list->name,
				 client->storage->hierarchy_sep,
				 list->flags);
	}

	send_ctx.client = client;
	send_ctx.response_name = response_name;
	send_ctx.sep = sep;
	send_ctx.sep_chr = sep_chr;
	send_ctx.glob = imap_match_init(data_stack_pool, mask, TRUE,
					client->storage->hierarchy_sep);
	send_ctx.listext = listext;
	send_ctx.no_placeholder = (list_flags & MAILBOX_LIST_SUBSCRIBED) == 0;

	list_send(&send_ctx, nodes, NULL);
	imap_match_deinit(send_ctx.glob);
	pool_unref(pool);
}

static void list_unsorted(struct client *client,
			  struct mailbox_list_context *ctx,
			  const char *reply, const char *sep, int listext)
{
	struct mailbox_list *list;
	string_t *str;

	while ((list = client->storage->list_mailbox_next(ctx)) != NULL) {
		t_push();
		str = t_str_new(256);
		str_printfa(str, "* %s (%s) \"%s\" ", reply,
			    mailbox_flags2str(list->flags, listext, FALSE),
			    sep);
		if (strcasecmp(list->name, "INBOX") == 0)
			str_append(str, "INBOX");
		else
			imap_quote_append_string(str, list->name);
		client_send_line(client, str_c(str));
		t_pop();
	}
}

static int parse_list_flags(struct client *client, struct imap_arg *args,
			    enum mailbox_list_flags *list_flags)
{
	const char *atom;

	while (args->type != IMAP_ARG_EOL) {
		if (args->type != IMAP_ARG_ATOM) {
			client_send_command_error(client,
				"List options contains non-atoms.");
			return FALSE;
		}

		atom = IMAP_ARG_STR(args);

		if (strcasecmp(atom, "SUBSCRIBED") == 0)
			*list_flags |= MAILBOX_LIST_SUBSCRIBED;
		else if (strcasecmp(atom, "CHILDREN") == 0)
			*list_flags |= MAILBOX_LIST_CHILDREN;
		else {
			client_send_tagline(client, t_strconcat(
				"BAD Invalid list option ", atom, NULL));
			return FALSE;
		}
		args++;
	}
	return TRUE;
}

int _cmd_list_full(struct client *client, int lsub)
{
	struct imap_arg *args;
        enum mailbox_list_flags list_flags;
	struct mailbox_list_context *ctx;
	const char *ref, *mask;
	char sep_chr, sep[3];
	int failed, sorted, listext;

	sep_chr = client->storage->hierarchy_sep;
	if (IS_ESCAPED_CHAR(sep_chr)) {
		sep[0] = '\\';
		sep[1] = sep_chr;
		sep[2] = '\0';
	} else {
		sep[0] = sep_chr;
		sep[1] = '\0';
	}

	/* [(<options>)] <reference> <mailbox wildcards> */
	if (!client_read_args(client, 0, 0, &args))
		return FALSE;

	listext = FALSE;
	if (lsub)
		list_flags = MAILBOX_LIST_SUBSCRIBED | MAILBOX_LIST_FAST_FLAGS;
	else {
		list_flags = 0;
		if (args[0].type == IMAP_ARG_LIST) {
			listext = TRUE;
			if (!parse_list_flags(client,
					      IMAP_ARG_LIST(&args[0])->args,
					      &list_flags))
				return TRUE;
			args++;
		}
	}

	ref = imap_arg_string(&args[0]);
	mask = imap_arg_string(&args[1]);

	if (ref == NULL || mask == NULL) {
		client_send_command_error(client, "Invalid FETCH arguments.");
		return TRUE;
	}

	if (*mask == '\0' && !lsub) {
		/* special request to return the hierarchy delimiter */
		client_send_line(client, t_strconcat(
			"* LIST (\\Noselect) \"", sep, "\" \"\"", NULL));
		failed = FALSE;
	} else {
		if (*ref != '\0') {
			/* join reference + mask */
			if (*mask == sep_chr &&
			    ref[strlen(ref)-1] == sep_chr) {
				/* LIST A. .B -> A.B */
				mask++;
			}
			if (*mask != sep_chr &&
			    ref[strlen(ref)-1] != sep_chr) {
				/* LIST A B -> A.B */
				mask = t_strconcat(ref, sep, mask, NULL);
			} else {
				mask = t_strconcat(ref, mask, NULL);
			}
		}

		ctx = client->storage->list_mailbox_init(client->storage, mask,
							 list_flags, &sorted);
		if (ctx == NULL)
			failed = TRUE;
		else {
			const char *response_name = lsub ? "LSUB" : "LIST";

			if (sorted) {
				list_unsorted(client, ctx, response_name, sep,
					      listext);
			} else {
				list_and_sort(client, ctx, response_name, mask,
					      sep, sep_chr, list_flags,
					      listext);
			}

			failed = !client->storage->list_mailbox_deinit(ctx);
		}
	}

	if (failed)
		client_send_storage_error(client);
	else {
		client_send_tagline(client, lsub ?
				    "OK Lsub completed." :
				    "OK List completed.");
	}
	return TRUE;
}

int cmd_list(struct client *client)
{
	return _cmd_list_full(client, FALSE);
}