2 * Copyright (C) 1996-9 Brandon Long <blong@fiction.net>
3 * Copyright (C) 1999-2007 Brendan Cully <brendan@kublai.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 /* Mutt browser support routines */
30 #include "imap_private.h"
32 /* -- forward declarations -- */
33 static int browse_add_list_result (IMAP_DATA* idata, const char* cmd,
34 struct browser_state* state, short isparent);
35 static void imap_add_folder (char delim, char *folder, int noselect,
36 int noinferiors, struct browser_state *state, short isparent);
37 static int compare_names(struct folder_file *a, struct folder_file *b);
39 /* imap_browse: IMAP hook into the folder browser, fills out browser_state,
40 * given a current folder to browse */
41 int imap_browse (char* path, struct browser_state* state)
45 char buf[LONG_STRING];
46 char buf2[LONG_STRING];
47 char mbox[LONG_STRING];
52 short showparents = 0;
56 if (imap_parse_path (path, &mx))
58 mutt_error (_("%s is an invalid IMAP path"), path);
62 save_lsub = option (OPTIMAPCHECKSUBSCRIBED);
63 unset_option (OPTIMAPCHECKSUBSCRIBED);
64 strfcpy (list_cmd, option (OPTIMAPLSUB) ? "LSUB" : "LIST", sizeof (list_cmd));
66 if (!(idata = imap_conn_find (&(mx.account), 0)))
69 mutt_message _("Getting folder list...");
71 /* skip check for parents when at the root */
72 if (mx.mbox && mx.mbox[0] != '\0')
75 imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox));
76 imap_munge_mbox_name (buf, sizeof (buf), mbox);
77 imap_unquote_string(buf); /* As kludgy as it gets */
78 mbox[sizeof (mbox) - 1] = '\0';
79 strncpy (mbox, buf, sizeof (mbox) - 1);
80 n = mutt_strlen (mbox);
82 dprint (3, (debugfile, "imap_browse: mbox: %s\n", mbox));
84 /* if our target exists and has inferiors, enter it if we
85 * aren't already going to */
86 if (mbox[n-1] != idata->delim)
88 snprintf (buf, sizeof (buf), "%s \"\" \"%s\"", list_cmd, mbox);
89 imap_cmd_start (idata, buf);
90 idata->cmdtype = IMAP_CT_LIST;
91 idata->cmddata = &list;
95 rc = imap_cmd_step (idata);
96 if (rc == IMAP_CMD_CONTINUE && list.name)
98 if (!list.noinferiors && list.name[0] &&
99 !imap_mxcmp (list.name, mbox) &&
100 (n = strlen (mbox)) < sizeof (mbox) - 1)
102 mbox[n++] = list.delim;
107 while (rc == IMAP_CMD_CONTINUE);
108 idata->cmddata = NULL;
111 /* if we're descending a folder, mark it as current in browser_state */
112 if (mbox[n-1] == list.delim)
115 imap_qualify_path (buf, sizeof (buf), &mx, mbox);
116 state->folder = safe_strdup (buf);
120 /* Find superiors to list
121 * Note: UW-IMAP servers return folder + delimiter when asked to list
122 * folder + delimiter. Cyrus servers don't. So we ask for folder,
123 * and tack on delimiter ourselves.
124 * Further note: UW-IMAP servers return nothing when asked for
125 * NAMESPACES without delimiters at the end. Argh! */
126 for (n--; n >= 0 && mbox[n] != list.delim ; n--);
127 if (n > 0) /* "aaaa/bbbb/" -> "aaaa" */
129 /* forget the check, it is too delicate (see above). Have we ever
130 * had the parent not exist? */
136 dprint (3, (debugfile, "imap_init_browse: adding parent %s\n", mbox));
137 imap_add_folder (list.delim, mbox, 1, 0, state, 1);
140 /* if our target isn't a folder, we are in our superior */
143 /* store folder with delimiter */
147 imap_qualify_path (buf, sizeof (buf), &mx, mbox);
148 state->folder = safe_strdup (buf);
152 /* "/bbbb/" -> add "/", "aaaa/" -> add "" */
156 /* folder may be "/" */
157 snprintf (relpath, sizeof (relpath), "%c" , n < 0 ? '\0' : idata->delim);
159 imap_add_folder (idata->delim, relpath, 1, 0, state, 1);
162 imap_qualify_path (buf, sizeof (buf), &mx, relpath);
163 state->folder = safe_strdup (buf);
170 /* no namespace, no folder: set folder to host only */
173 imap_qualify_path (buf, sizeof (buf), &mx, NULL);
174 state->folder = safe_strdup (buf);
177 nsup = state->entrylen;
179 dprint (3, (debugfile, "imap_browse: Quoting mailbox scan: %s -> ", mbox));
180 snprintf (buf, sizeof (buf), "%s%%", mbox);
181 imap_quote_string (buf2, sizeof (buf2), buf);
182 dprint (3, (debugfile, "%s\n", buf2));
183 snprintf (buf, sizeof (buf), "%s \"\" %s", list_cmd, buf2);
184 if (browse_add_list_result (idata, buf, state, 0))
187 if (!state->entrylen)
189 mutt_error _("No such folder");
195 qsort(&(state->entry[nsup]),state->entrylen-nsup,sizeof(state->entry[0]),
196 (int (*)(const void*,const void*)) compare_names);
199 set_option (OPTIMAPCHECKSUBSCRIBED);
206 set_option (OPTIMAPCHECKSUBSCRIBED);
211 int imap_mailbox_state (const char* path, struct mailbox_state* state)
217 memset (state, 0, sizeof (*state));
218 if (imap_parse_path (path, &mx) < 0)
220 dprint (1, (debugfile, "imap_mailbox_state: bad path %s\n", path));
223 if (!(idata = imap_conn_find (&mx.account, M_IMAP_CONN_NONEW)))
225 dprint (2, (debugfile, "imap_mailbox_state: no open connection for %s\n",
231 if (!imap_mxcmp(mx.mbox, idata->mailbox))
233 state->new = idata->ctx->new;
234 state->messages = idata->ctx->msgcount;
236 else if ((status = imap_mboxcache_get (idata, mx.mbox, 0)))
238 state->new = status->unseen;
239 state->messages = status->messages;
245 /* imap_mailbox_create: Prompt for a new mailbox name, and try to create it */
246 int imap_mailbox_create (const char* folder)
250 char buf[LONG_STRING];
253 if (imap_parse_path (folder, &mx) < 0)
255 dprint (1, (debugfile, "imap_mailbox_create: Bad starting path %s\n",
260 if (!(idata = imap_conn_find (&mx.account, M_IMAP_CONN_NONEW)))
262 dprint (1, (debugfile, "imap_mailbox_create: Couldn't find open connection to %s", mx.account.host));
266 strfcpy (buf, NONULL (mx.mbox), sizeof (buf));
268 /* append a delimiter if necessary */
269 n = mutt_strlen (buf);
270 if (n && (n < sizeof (buf) - 1) && (buf[n-1] != idata->delim))
272 buf[n++] = idata->delim;
276 if (mutt_get_field (_("Create mailbox: "), buf, sizeof (buf), M_FILE) < 0)
279 if (!mutt_strlen (buf))
281 mutt_error (_("Mailbox must have a name."));
286 if (imap_create_mailbox (idata, buf) < 0)
289 mutt_message _("Mailbox created.");
300 int imap_mailbox_rename(const char* mailbox)
304 char buf[LONG_STRING];
305 char newname[SHORT_STRING];
307 if (imap_parse_path (mailbox, &mx) < 0)
309 dprint (1, (debugfile, "imap_mailbox_rename: Bad source mailbox %s\n",
314 if (!(idata = imap_conn_find (&mx.account, M_IMAP_CONN_NONEW)))
316 dprint (1, (debugfile, "imap_mailbox_rename: Couldn't find open connection to %s", mx.account.host));
320 snprintf(buf, sizeof (buf), _("Rename mailbox %s to: "), mx.mbox);
322 if (mutt_get_field (buf, newname, sizeof (newname), M_FILE) < 0)
325 if (!mutt_strlen (newname))
327 mutt_error (_("Mailbox must have a name."));
332 if (imap_rename_mailbox (idata, &mx, newname) < 0) {
333 mutt_error (_("Rename failed: %s"), imap_get_qualifier (idata->buf));
338 mutt_message (_("Mailbox renamed."));
349 static int browse_add_list_result (IMAP_DATA* idata, const char* cmd,
350 struct browser_state* state, short isparent)
356 if (imap_parse_path (state->folder, &mx))
358 dprint (2, (debugfile,
359 "browse_add_list_result: current folder %s makes no sense\n", state->folder));
363 imap_cmd_start (idata, cmd);
364 idata->cmdtype = IMAP_CT_LIST;
365 idata->cmddata = &list;
369 rc = imap_cmd_step (idata);
371 if (rc == IMAP_CMD_CONTINUE && list.name)
373 /* Let a parent folder never be selectable for navigation */
376 /* prune current folder from output */
377 if (isparent || mutt_strncmp (list.name, mx.mbox, strlen (list.name)))
378 imap_add_folder (list.delim, list.name, list.noselect, list.noinferiors,
382 while (rc == IMAP_CMD_CONTINUE);
383 idata->cmddata = NULL;
386 return rc == IMAP_CMD_OK ? 0 : -1;
389 /* imap_add_folder: add a folder name to the browser list, formatting it as
391 static void imap_add_folder (char delim, char *folder, int noselect,
392 int noinferiors, struct browser_state *state, short isparent)
394 char tmp[LONG_STRING];
395 char relpath[LONG_STRING];
398 if (imap_parse_path (state->folder, &mx))
401 imap_unmunge_mbox_name (folder);
403 if (state->entrylen + 1 == state->entrymax)
405 safe_realloc (&state->entry,
406 sizeof (struct folder_file) * (state->entrymax += 256));
407 memset (state->entry + state->entrylen, 0,
408 (sizeof (struct folder_file) * (state->entrymax - state->entrylen)));
411 /* render superiors as unix-standard ".." */
413 strfcpy (relpath, "../", sizeof (relpath));
414 /* strip current folder from target, to render a relative path */
415 else if (!mutt_strncmp (mx.mbox, folder, mutt_strlen (mx.mbox)))
416 strfcpy (relpath, folder + mutt_strlen (mx.mbox), sizeof (relpath));
418 strfcpy (relpath, folder, sizeof (relpath));
420 /* apply filemask filter. This should really be done at menu setup rather
421 * than at scan, since it's so expensive to scan. But that's big changes
423 if (!((regexec (Mask.rx, relpath, 0, NULL, 0) == 0) ^ Mask.not))
429 imap_qualify_path (tmp, sizeof (tmp), &mx, folder);
430 (state->entry)[state->entrylen].name = safe_strdup (tmp);
432 /* mark desc with delim in browser if it can have subfolders */
433 if (!isparent && !noinferiors && strlen (relpath) < sizeof (relpath) - 1)
435 relpath[strlen (relpath) + 1] = '\0';
436 relpath[strlen (relpath)] = delim;
439 (state->entry)[state->entrylen].desc = safe_strdup (relpath);
441 (state->entry)[state->entrylen].imap = 1;
442 /* delimiter at the root is useless. */
443 if (folder[0] == '\0')
445 (state->entry)[state->entrylen].delim = delim;
446 (state->entry)[state->entrylen].selectable = !noselect;
447 (state->entry)[state->entrylen].inferiors = !noinferiors;
453 static int compare_names(struct folder_file *a, struct folder_file *b)
455 return mutt_strcmp(a->name, b->name);