]> granicus.if.org Git - mutt/blob - browser.c
Convert smime_invoke_import() and helpers to use buffer pool.
[mutt] / browser.c
1 /*
2  * Copyright (C) 1996-2000,2007,2010,2013 Michael R. Elkins <me@mutt.org>
3  *
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  *
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  *
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #if HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include "mutt.h"
24 #include "mutt_curses.h"
25 #include "mutt_menu.h"
26 #include "attach.h"
27 #include "buffy.h"
28 #include "mapping.h"
29 #include "sort.h"
30 #include "mailbox.h"
31 #include "browser.h"
32 #ifdef USE_IMAP
33 #include "imap.h"
34 #endif
35
36 #include <stdlib.h>
37 #include <dirent.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <unistd.h>
41 #include <sys/stat.h>
42 #include <errno.h>
43 #include <locale.h>
44
45 static const struct mapping_t FolderHelp[] = {
46   { N_("Exit"),  OP_EXIT },
47   { N_("Chdir"), OP_CHANGE_DIRECTORY },
48   { N_("Mask"),  OP_ENTER_MASK },
49   { N_("Help"),  OP_HELP },
50   { NULL,        0 }
51 };
52
53 typedef struct folder_t
54 {
55   struct folder_file *ff;
56   int num;
57 } FOLDER;
58
59 static BUFFER *LastDir = NULL;
60 static BUFFER *LastDirBackup = NULL;
61
62 void mutt_browser_cleanup (void)
63 {
64   mutt_buffer_free (&LastDir);
65   mutt_buffer_free (&LastDirBackup);
66 }
67
68 /* Frees up the memory allocated for the local-global variables.  */
69 static void destroy_state (struct browser_state *state)
70 {
71   int c;
72
73   for (c = 0; c < state->entrylen; c++)
74   {
75     FREE (&((state->entry)[c].display_name));
76     FREE (&((state->entry)[c].full_path));
77   }
78 #ifdef USE_IMAP
79   FREE (&state->folder);
80 #endif
81   FREE (&state->entry);
82 }
83
84 static int browser_compare_subject (const void *a, const void *b)
85 {
86   struct folder_file *pa = (struct folder_file *) a;
87   struct folder_file *pb = (struct folder_file *) b;
88
89   int r = mutt_strcoll (pa->display_name, pb->display_name);
90
91   return ((BrowserSort & SORT_REVERSE) ? -r : r);
92 }
93
94 static int browser_compare_date (const void *a, const void *b)
95 {
96   struct folder_file *pa = (struct folder_file *) a;
97   struct folder_file *pb = (struct folder_file *) b;
98
99   int r = pa->mtime - pb->mtime;
100
101   return ((BrowserSort & SORT_REVERSE) ? -r : r);
102 }
103
104 static int browser_compare_size (const void *a, const void *b)
105 {
106   struct folder_file *pa = (struct folder_file *) a;
107   struct folder_file *pb = (struct folder_file *) b;
108
109   int r = pa->size - pb->size;
110
111   return ((BrowserSort & SORT_REVERSE) ? -r : r);
112 }
113
114 static int browser_compare_count (const void *a, const void *b)
115 {
116   struct folder_file *pa = (struct folder_file *) a;
117   struct folder_file *pb = (struct folder_file *) b;
118
119   int r = pa->msg_count - pb->msg_count;
120
121   return ((BrowserSort & SORT_REVERSE) ? -r : r);
122 }
123
124 static int browser_compare_unread (const void *a, const void *b)
125 {
126   struct folder_file *pa = (struct folder_file *) a;
127   struct folder_file *pb = (struct folder_file *) b;
128
129   int r = pa->msg_unread - pb->msg_unread;
130
131   return ((BrowserSort & SORT_REVERSE) ? -r : r);
132 }
133
134 static void browser_sort (struct browser_state *state)
135 {
136   int (*f) (const void *, const void *);
137
138   switch (BrowserSort & SORT_MASK)
139   {
140     case SORT_ORDER:
141       return;
142     case SORT_DATE:
143       f = browser_compare_date;
144       break;
145     case SORT_SIZE:
146       f = browser_compare_size;
147       break;
148     case SORT_COUNT:
149       f = browser_compare_count;
150       break;
151     case SORT_UNREAD:
152       f = browser_compare_unread;
153       break;
154     case SORT_SUBJECT:
155     default:
156       f = browser_compare_subject;
157       break;
158   }
159   qsort (state->entry, state->entrylen, sizeof (struct folder_file), f);
160 }
161
162 static int link_is_dir (const char *full_path)
163 {
164   struct stat st;
165   int retval = 0;
166
167   if (stat (full_path, &st) == 0)
168     retval = S_ISDIR (st.st_mode);
169
170   return retval;
171 }
172
173 static const char *
174 folder_format_str (char *dest, size_t destlen, size_t col, int cols, char op, const char *src,
175                    const char *fmt, const char *ifstring, const char *elsestring,
176                    unsigned long data, format_flag flags)
177 {
178   char fn[SHORT_STRING], tmp[SHORT_STRING], permission[11];
179   char date[SHORT_STRING], *t_fmt;
180   time_t tnow;
181   FOLDER *folder = (FOLDER *) data;
182   struct passwd *pw;
183   struct group *gr;
184   int optional = (flags & MUTT_FORMAT_OPTIONAL);
185
186   switch (op)
187   {
188     case 'C':
189       snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
190       snprintf (dest, destlen, tmp, folder->num + 1);
191       break;
192
193     case 'd':
194     case 'D':
195       if (folder->ff->local)
196       {
197         int do_locales = TRUE;
198
199         if (op == 'D')
200         {
201           t_fmt = NONULL(DateFmt);
202           if (*t_fmt == '!')
203           {
204             ++t_fmt;
205             do_locales = FALSE;
206           }
207         }
208         else
209         {
210           tnow = time (NULL);
211           t_fmt = tnow - folder->ff->mtime < 31536000 ? "%b %d %H:%M" : "%b %d  %Y";
212         }
213
214         if (!do_locales)
215           setlocale (LC_TIME, "C");
216         strftime (date, sizeof (date), t_fmt, localtime (&folder->ff->mtime));
217         if (!do_locales)
218           setlocale (LC_TIME, "");
219
220         mutt_format_s (dest, destlen, fmt, date);
221       }
222       else
223         mutt_format_s (dest, destlen, fmt, "");
224       break;
225
226     case 'f':
227     {
228       char *s = NONULL (folder->ff->display_name);
229
230       snprintf (fn, sizeof (fn), "%s%s", s,
231                 folder->ff->local ?
232                 (S_ISLNK (folder->ff->mode) ?
233                  "@" :
234                  (S_ISDIR (folder->ff->mode) ?
235                   "/" :
236                   ((folder->ff->mode & S_IXUSR) != 0 ?
237                    "*" :
238                    ""))) :
239                 "");
240
241       mutt_format_s (dest, destlen, fmt, fn);
242       break;
243     }
244     case 'F':
245       if (folder->ff->local)
246       {
247         snprintf (permission, sizeof (permission), "%c%c%c%c%c%c%c%c%c%c",
248                   S_ISDIR(folder->ff->mode) ? 'd' : (S_ISLNK(folder->ff->mode) ? 'l' : '-'),
249                   (folder->ff->mode & S_IRUSR) != 0 ? 'r': '-',
250                   (folder->ff->mode & S_IWUSR) != 0 ? 'w' : '-',
251                   (folder->ff->mode & S_ISUID) != 0 ? 's' : (folder->ff->mode & S_IXUSR) != 0 ? 'x': '-',
252                   (folder->ff->mode & S_IRGRP) != 0 ? 'r' : '-',
253                   (folder->ff->mode & S_IWGRP) != 0 ? 'w' : '-',
254                   (folder->ff->mode & S_ISGID) != 0 ? 's' : (folder->ff->mode & S_IXGRP) != 0 ? 'x': '-',
255                   (folder->ff->mode & S_IROTH) != 0 ? 'r' : '-',
256                   (folder->ff->mode & S_IWOTH) != 0 ? 'w' : '-',
257                   (folder->ff->mode & S_ISVTX) != 0 ? 't' : (folder->ff->mode & S_IXOTH) != 0 ? 'x': '-');
258         mutt_format_s (dest, destlen, fmt, permission);
259       }
260 #ifdef USE_IMAP
261       else if (folder->ff->imap)
262       {
263         /* mark folders with subfolders AND mail */
264         snprintf (permission, sizeof (permission), "IMAP %c",
265                   (folder->ff->inferiors && folder->ff->selectable) ? '+' : ' ');
266         mutt_format_s (dest, destlen, fmt, permission);
267       }
268 #endif
269       else
270         mutt_format_s (dest, destlen, fmt, "");
271       break;
272
273     case 'g':
274       if (folder->ff->local)
275       {
276         if ((gr = getgrgid (folder->ff->gid)))
277           mutt_format_s (dest, destlen, fmt, gr->gr_name);
278         else
279         {
280           snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
281           snprintf (dest, destlen, tmp, folder->ff->gid);
282         }
283       }
284       else
285         mutt_format_s (dest, destlen, fmt, "");
286       break;
287
288     case 'l':
289       if (folder->ff->local)
290       {
291         snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
292         snprintf (dest, destlen, tmp, folder->ff->nlink);
293       }
294       else
295         mutt_format_s (dest, destlen, fmt, "");
296       break;
297
298     case 'm':
299       if (!optional)
300       {
301         if (folder->ff->has_buffy)
302         {
303           snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
304           snprintf (dest, destlen, tmp, folder->ff->msg_count);
305         }
306         else
307           mutt_format_s (dest, destlen, fmt, "");
308       }
309       else if (!folder->ff->msg_count)
310         optional = 0;
311       break;
312
313     case 'N':
314       snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
315       snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : ' ');
316       break;
317
318     case 'n':
319       if (!optional)
320       {
321         if (folder->ff->has_buffy)
322         {
323           snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
324           snprintf (dest, destlen, tmp, folder->ff->msg_unread);
325         }
326         else
327           mutt_format_s (dest, destlen, fmt, "");
328       }
329       else if (!folder->ff->msg_unread)
330         optional = 0;
331       break;
332
333     case 's':
334       if (folder->ff->local)
335       {
336         mutt_pretty_size(fn, sizeof(fn), folder->ff->size);
337         snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
338         snprintf (dest, destlen, tmp, fn);
339       }
340       else
341         mutt_format_s (dest, destlen, fmt, "");
342       break;
343
344     case 't':
345       snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
346       snprintf (dest, destlen, tmp, folder->ff->tagged ? '*' : ' ');
347       break;
348
349     case 'u':
350       if (folder->ff->local)
351       {
352         if ((pw = getpwuid (folder->ff->uid)))
353           mutt_format_s (dest, destlen, fmt, pw->pw_name);
354         else
355         {
356           snprintf (tmp, sizeof (tmp), "%%%sld", fmt);
357           snprintf (dest, destlen, tmp, folder->ff->uid);
358         }
359       }
360       else
361         mutt_format_s (dest, destlen, fmt, "");
362       break;
363
364     default:
365       snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
366       snprintf (dest, destlen, tmp, op);
367       break;
368   }
369
370   if (optional)
371     mutt_FormatString (dest, destlen, col, cols, ifstring, folder_format_str, data, 0);
372   else if (flags & MUTT_FORMAT_OPTIONAL)
373     mutt_FormatString (dest, destlen, col, cols, elsestring, folder_format_str, data, 0);
374
375   return (src);
376 }
377
378 static void add_folder (MUTTMENU *m, struct browser_state *state,
379                         const char *display_name, const char *full_path,
380                         const struct stat *s, BUFFY *b)
381 {
382   if (state->entrylen == state->entrymax)
383   {
384     /* need to allocate more space */
385     safe_realloc (&state->entry,
386                   sizeof (struct folder_file) * (state->entrymax += 256));
387     memset (&state->entry[state->entrylen], 0,
388             sizeof (struct folder_file) * 256);
389     if (m)
390       m->data = state->entry;
391   }
392
393   if (s != NULL)
394   {
395     (state->entry)[state->entrylen].mode = s->st_mode;
396     (state->entry)[state->entrylen].mtime = s->st_mtime;
397     (state->entry)[state->entrylen].size = s->st_size;
398     (state->entry)[state->entrylen].gid = s->st_gid;
399     (state->entry)[state->entrylen].uid = s->st_uid;
400     (state->entry)[state->entrylen].nlink = s->st_nlink;
401
402     (state->entry)[state->entrylen].local = 1;
403   }
404
405   if (b)
406   {
407     (state->entry)[state->entrylen].has_buffy = 1;
408     (state->entry)[state->entrylen].new = b->new;
409     (state->entry)[state->entrylen].msg_count = b->msg_count;
410     (state->entry)[state->entrylen].msg_unread = b->msg_unread;
411   }
412
413   (state->entry)[state->entrylen].display_name = safe_strdup (display_name);
414   (state->entry)[state->entrylen].full_path = safe_strdup (full_path);
415 #ifdef USE_IMAP
416   (state->entry)[state->entrylen].imap = 0;
417 #endif
418   (state->entrylen)++;
419 }
420
421 static void init_state (struct browser_state *state, MUTTMENU *menu)
422 {
423   state->entrylen = 0;
424   state->entrymax = 256;
425   state->entry = (struct folder_file *) safe_calloc (state->entrymax, sizeof (struct folder_file));
426 #ifdef USE_IMAP
427   state->imap_browse = 0;
428 #endif
429   if (menu)
430     menu->data = state->entry;
431 }
432
433 static int examine_directory (MUTTMENU *menu, struct browser_state *state,
434                               const char *d, const char *prefix)
435 {
436   struct stat s;
437   DIR *dp;
438   struct dirent *de;
439   BUFFER *full_path = NULL;
440   BUFFY *tmp;
441
442   while (stat (d, &s) == -1)
443   {
444     if (errno == ENOENT)
445     {
446       /* The last used directory is deleted, try to use the parent dir. */
447       char *c = strrchr (d, '/');
448
449       if (c && (c > d))
450       {
451         *c = 0;
452         continue;
453       }
454     }
455     mutt_perror (d);
456     return (-1);
457   }
458
459   if (!S_ISDIR (s.st_mode))
460   {
461     mutt_error (_("%s is not a directory."), d);
462     return (-1);
463   }
464
465   mutt_buffy_check (0);
466
467   if ((dp = opendir (d)) == NULL)
468   {
469     mutt_perror (d);
470     return (-1);
471   }
472
473   full_path = mutt_buffer_pool_get ();
474   init_state (state, menu);
475
476   while ((de = readdir (dp)) != NULL)
477   {
478     if (mutt_strcmp (de->d_name, ".") == 0)
479       continue;    /* we don't need . */
480
481     if (prefix && *prefix && mutt_strncmp (prefix, de->d_name, mutt_strlen (prefix)) != 0)
482       continue;
483     if (!((regexec (Mask.rx, de->d_name, 0, NULL, 0) == 0) ^ Mask.not))
484       continue;
485
486     mutt_buffer_concat_path (full_path, d, de->d_name);
487     if (lstat (mutt_b2s (full_path), &s) == -1)
488       continue;
489
490     /* No size for directories or symlinks */
491     if (S_ISDIR (s.st_mode) || S_ISLNK (s.st_mode))
492       s.st_size = 0;
493     else if (! S_ISREG (s.st_mode))
494       continue;
495
496     tmp = Incoming;
497     while (tmp && mutt_strcmp (mutt_b2s (full_path), mutt_b2s (tmp->pathbuf)))
498       tmp = tmp->next;
499     if (tmp && Context &&
500         !mutt_strcmp (tmp->realpath, Context->realpath))
501     {
502       tmp->msg_count = Context->msgcount;
503       tmp->msg_unread = Context->unread;
504     }
505     add_folder (menu, state, de->d_name, mutt_b2s (full_path), &s, tmp);
506   }
507   closedir (dp);
508   browser_sort (state);
509
510   mutt_buffer_pool_release (&full_path);
511   return 0;
512 }
513
514 static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state)
515 {
516   struct stat s;
517   BUFFY *tmp = Incoming;
518   BUFFER *mailbox = NULL;
519   BUFFER *md = NULL;
520
521   if (!Incoming)
522     return (-1);
523   mutt_buffy_check (0);
524
525   mailbox = mutt_buffer_pool_get ();
526   md = mutt_buffer_pool_get ();
527   init_state (state, menu);
528
529   do
530   {
531     if (Context &&
532         !mutt_strcmp (tmp->realpath, Context->realpath))
533     {
534       tmp->msg_count = Context->msgcount;
535       tmp->msg_unread = Context->unread;
536     }
537
538     mutt_buffer_strcpy (mailbox, mutt_b2s (tmp->pathbuf));
539     if (option (OPTBROWSERABBRMAILBOXES))
540       mutt_buffer_pretty_mailbox (mailbox);
541
542 #ifdef USE_IMAP
543     if (mx_is_imap (mutt_b2s (tmp->pathbuf)))
544     {
545       add_folder (menu, state, mutt_b2s (mailbox), mutt_b2s (tmp->pathbuf), NULL, tmp);
546       continue;
547     }
548 #endif
549 #ifdef USE_POP
550     if (mx_is_pop (mutt_b2s (tmp->pathbuf)))
551     {
552       add_folder (menu, state, mutt_b2s (mailbox), mutt_b2s (tmp->pathbuf), NULL, tmp);
553       continue;
554     }
555 #endif
556     if (lstat (mutt_b2s (tmp->pathbuf), &s) == -1)
557       continue;
558
559     if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) &&
560         (! S_ISLNK (s.st_mode)))
561       continue;
562
563     if (mx_is_maildir (mutt_b2s (tmp->pathbuf)))
564     {
565       struct stat st2;
566
567       mutt_buffer_printf (md, "%s/new", mutt_b2s (tmp->pathbuf));
568       if (stat (mutt_b2s (md), &s) < 0)
569         s.st_mtime = 0;
570       mutt_buffer_printf (md, "%s/cur", mutt_b2s (tmp->pathbuf));
571       if (stat (mutt_b2s (md), &st2) < 0)
572         st2.st_mtime = 0;
573       if (st2.st_mtime > s.st_mtime)
574         s.st_mtime = st2.st_mtime;
575     }
576
577     add_folder (menu, state, mutt_b2s (mailbox), mutt_b2s (tmp->pathbuf), &s, tmp);
578   }
579   while ((tmp = tmp->next));
580   browser_sort (state);
581
582   mutt_buffer_pool_release (&mailbox);
583   mutt_buffer_pool_release (&md);
584   return 0;
585 }
586
587 static int select_file_search (MUTTMENU *menu, regex_t *re, int n)
588 {
589   return (regexec (re, ((struct folder_file *) menu->data)[n].display_name, 0, NULL, 0));
590 }
591
592 static void folder_entry (char *s, size_t slen, MUTTMENU *menu, int num)
593 {
594   FOLDER folder;
595
596   folder.ff = &((struct folder_file *) menu->data)[num];
597   folder.num = num;
598
599   mutt_FormatString (s, slen, 0, MuttIndexWindow->cols, NONULL(FolderFormat), folder_format_str,
600                      (unsigned long) &folder, MUTT_FORMAT_ARROWCURSOR);
601 }
602
603 static void set_sticky_cursor (struct browser_state *state, MUTTMENU *menu, const char *defaultsel)
604 {
605   int i;
606
607   if (option (OPTBROWSERSTICKYCURSOR) && defaultsel && *defaultsel)
608   {
609     for (i = 0; i < menu->max; i++)
610     {
611       if (!mutt_strcmp (defaultsel, state->entry[i].full_path))
612       {
613         menu->current = i;
614         break;
615       }
616     }
617   }
618 }
619
620 static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title,
621                        size_t titlelen, int buffy, const char *defaultsel)
622 {
623   BUFFER *path = NULL;
624
625   path = mutt_buffer_pool_get ();
626
627   menu->max = state->entrylen;
628
629   if (menu->current >= menu->max)
630     menu->current = menu->max - 1;
631   if (menu->current < 0)
632     menu->current = 0;
633   if (menu->top > menu->current)
634     menu->top = 0;
635
636   menu->tagged = 0;
637
638   if (buffy)
639     snprintf (title, titlelen, _("Mailboxes [%d]"), mutt_buffy_check (0));
640   else
641   {
642     mutt_buffer_strcpy (path, mutt_b2s (LastDir));
643     mutt_buffer_pretty_mailbox (path);
644 #ifdef USE_IMAP
645     if (state->imap_browse && option (OPTIMAPLSUB))
646       snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
647                 mutt_b2s (path), NONULL (Mask.pattern));
648     else
649 #endif
650       snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
651                 mutt_b2s (path), NONULL(Mask.pattern));
652   }
653   menu->redraw = REDRAW_FULL;
654
655   set_sticky_cursor (state, menu, defaultsel);
656
657   mutt_buffer_pool_release (&path);
658 }
659
660 static int file_tag (MUTTMENU *menu, int n, int m)
661 {
662   struct folder_file *ff = &(((struct folder_file *)menu->data)[n]);
663   int ot;
664   if (S_ISDIR (ff->mode) ||
665       (S_ISLNK (ff->mode) && link_is_dir (ff->full_path)))
666   {
667     mutt_error _("Can't attach a directory!");
668     return 0;
669   }
670
671   ot = ff->tagged;
672   ff->tagged = (m >= 0 ? m : !ff->tagged);
673
674   return ff->tagged - ot;
675 }
676
677 void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *numfiles)
678 {
679   BUFFER *f_buf = NULL;
680
681   f_buf = mutt_buffer_pool_get ();
682
683   mutt_buffer_strcpy (f_buf, NONULL (f));
684   _mutt_buffer_select_file (f_buf, flags, files, numfiles);
685   strfcpy (f, mutt_b2s (f_buf), flen);
686
687   mutt_buffer_pool_release (&f_buf);
688 }
689
690 void _mutt_buffer_select_file (BUFFER *f, int flags, char ***files, int *numfiles)
691 {
692   BUFFER *buf = NULL;
693   BUFFER *prefix = NULL;
694   BUFFER *tmp = NULL;
695   BUFFER *OldLastDir = NULL;
696   BUFFER *defaultsel = NULL;
697   char helpstr[LONG_STRING];
698   char title[STRING];
699   struct browser_state state;
700   MUTTMENU *menu = NULL;
701   struct stat st;
702   int op, killPrefix = 0;
703   int i, j;
704   int multiple = (flags & MUTT_SEL_MULTI)  ? 1 : 0;
705   int folder   = (flags & MUTT_SEL_FOLDER) ? 1 : 0;
706   int buffy    = (flags & MUTT_SEL_BUFFY)  ? 1 : 0;
707
708   buffy = buffy && folder;
709
710   buf        = mutt_buffer_pool_get ();
711   prefix     = mutt_buffer_pool_get ();
712   tmp        = mutt_buffer_pool_get ();
713   OldLastDir = mutt_buffer_pool_get ();
714   defaultsel  = mutt_buffer_pool_get ();
715
716   memset (&state, 0, sizeof (struct browser_state));
717
718   if (!LastDir)
719   {
720     LastDir = mutt_buffer_new ();
721     mutt_buffer_increase_size (LastDir, _POSIX_PATH_MAX);
722     LastDirBackup = mutt_buffer_new ();
723     mutt_buffer_increase_size (LastDirBackup, _POSIX_PATH_MAX);
724   }
725
726   if (!folder)
727     mutt_buffer_strcpy (LastDirBackup, mutt_b2s (LastDir));
728
729   if (*(mutt_b2s (f)))
730   {
731     mutt_buffer_expand_path (f);
732 #ifdef USE_IMAP
733     if (mx_is_imap (mutt_b2s (f)))
734     {
735       init_state (&state, NULL);
736       state.imap_browse = 1;
737       if (!imap_browse (mutt_b2s (f), &state))
738         mutt_buffer_strcpy (LastDir, state.folder);
739     }
740     else
741     {
742 #endif
743       for (i = mutt_buffer_len (f) - 1;
744            i > 0 && (mutt_b2s (f))[i] != '/' ;
745            i--);
746       if (i > 0)
747       {
748         if ((mutt_b2s (f))[0] == '/')
749           mutt_buffer_strcpy_n (LastDir, mutt_b2s (f), i);
750         else
751         {
752           mutt_getcwd (LastDir);
753           mutt_buffer_addch (LastDir, '/');
754           mutt_buffer_addstr_n (LastDir, mutt_b2s (f), i);
755         }
756       }
757       else
758       {
759         if ((mutt_b2s (f))[0] == '/')
760           mutt_buffer_strcpy (LastDir, "/");
761         else
762           mutt_getcwd (LastDir);
763       }
764
765       if (i <= 0 && (mutt_b2s (f))[0] != '/')
766         mutt_buffer_strcpy (prefix, mutt_b2s (f));
767       else
768         mutt_buffer_strcpy (prefix, mutt_b2s (f) + i + 1);
769       killPrefix = 1;
770 #ifdef USE_IMAP
771     }
772 #endif
773   }
774   else
775   {
776     if (!folder)
777       mutt_getcwd (LastDir);
778     else if (!*(mutt_b2s (LastDir)))
779       mutt_buffer_strcpy (LastDir, NONULL(Maildir));
780
781     if (Context)
782       mutt_buffer_strcpy (defaultsel, NONULL (Context->path));
783
784 #ifdef USE_IMAP
785     if (!buffy && mx_is_imap (mutt_b2s (LastDir)))
786     {
787       init_state (&state, NULL);
788       state.imap_browse = 1;
789       imap_browse (mutt_b2s (LastDir), &state);
790       browser_sort (&state);
791     }
792     else
793 #endif
794     {
795       i = mutt_buffer_len (LastDir);
796       while (i && mutt_b2s (LastDir)[--i] == '/')
797         LastDir->data[i] = '\0';
798       mutt_buffer_fix_dptr (LastDir);
799       if (!*(mutt_b2s (LastDir)))
800         mutt_getcwd (LastDir);
801     }
802   }
803
804   mutt_buffer_clear (f);
805
806   if (buffy)
807   {
808     if (examine_mailboxes (NULL, &state) == -1)
809       goto bail;
810   }
811   else
812 #ifdef USE_IMAP
813     if (!state.imap_browse)
814 #endif
815       if (examine_directory (NULL, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
816         goto bail;
817
818   menu = mutt_new_menu (MENU_FOLDER);
819   menu->make_entry = folder_entry;
820   menu->search = select_file_search;
821   menu->title = title;
822   menu->data = state.entry;
823   if (multiple)
824     menu->tag = file_tag;
825
826   menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER,
827                                   FolderHelp);
828   mutt_push_current_menu (menu);
829
830   init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
831
832   FOREVER
833   {
834     op = mutt_menuLoop (menu);
835
836     if (state.entrylen)
837       mutt_buffer_strcpy (defaultsel, state.entry[menu->current].full_path);
838
839     switch (op)
840     {
841       case OP_DESCEND_DIRECTORY:
842       case OP_GENERIC_SELECT_ENTRY:
843
844         if (!state.entrylen)
845         {
846           mutt_error _("No files match the file mask");
847           break;
848         }
849
850         if (S_ISDIR (state.entry[menu->current].mode) ||
851             (S_ISLNK (state.entry[menu->current].mode) &&
852              link_is_dir (state.entry[menu->current].full_path))
853 #ifdef USE_IMAP
854             || state.entry[menu->current].inferiors
855 #endif
856           )
857         {
858           if (op == OP_DESCEND_DIRECTORY
859               || (mx_get_magic (state.entry[menu->current].full_path) <= 0)
860 #ifdef USE_IMAP
861               || state.entry[menu->current].inferiors
862 #endif
863             )
864           {
865             /* save the old directory */
866             mutt_buffer_strcpy (OldLastDir, mutt_b2s (LastDir));
867
868             mutt_buffer_strcpy (defaultsel, mutt_b2s (OldLastDir));
869             if (mutt_buffer_len (defaultsel) && (*(defaultsel->dptr - 1) == '/'))
870             {
871               defaultsel->dptr--;
872               *(defaultsel->dptr) = '\0';
873             }
874
875             if (mutt_strcmp (state.entry[menu->current].display_name, "..") == 0)
876             {
877               size_t lastdirlen = mutt_buffer_len (LastDir);
878
879               if ((lastdirlen > 1) &&
880                   mutt_strcmp ("..", mutt_b2s (LastDir) + lastdirlen - 2) == 0)
881               {
882                 mutt_buffer_addstr (LastDir, "/..");
883               }
884               else
885               {
886                 char *p = NULL;
887                 if (lastdirlen > 1)
888                   p = strrchr (mutt_b2s (LastDir) + 1, '/');
889
890                 if (p)
891                 {
892                   *p = 0;
893                   mutt_buffer_fix_dptr (LastDir);
894                 }
895                 else
896                 {
897                   if (mutt_b2s (LastDir)[0] == '/')
898                     mutt_buffer_strcpy (LastDir, "/");
899                   else
900                     mutt_buffer_addstr (LastDir, "/..");
901                 }
902               }
903             }
904             else if (buffy)
905             {
906               mutt_buffer_strcpy (LastDir, state.entry[menu->current].full_path);
907             }
908 #ifdef USE_IMAP
909             else if (state.imap_browse)
910             {
911               ciss_url_t url;
912
913               mutt_buffer_strcpy (LastDir, state.entry[menu->current].full_path);
914               /* tack on delimiter here */
915
916               /* special case "" needs no delimiter */
917               url_parse_ciss (&url, state.entry[menu->current].full_path);
918               if (url.path &&
919                   (state.entry[menu->current].delim != '\0'))
920               {
921                 mutt_buffer_addch (LastDir, state.entry[menu->current].delim);
922               }
923             }
924 #endif
925             else
926             {
927               mutt_buffer_strcpy (LastDir, state.entry[menu->current].full_path);
928             }
929
930             destroy_state (&state);
931             if (killPrefix)
932             {
933               mutt_buffer_clear (prefix);
934               killPrefix = 0;
935             }
936             buffy = 0;
937 #ifdef USE_IMAP
938             if (state.imap_browse)
939             {
940               init_state (&state, NULL);
941               state.imap_browse = 1;
942               imap_browse (mutt_b2s (LastDir), &state);
943               browser_sort (&state);
944               menu->data = state.entry;
945             }
946             else
947 #endif
948               if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
949               {
950                 /* try to restore the old values */
951                 mutt_buffer_strcpy (LastDir, mutt_b2s (OldLastDir));
952                 if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
953                 {
954                   mutt_buffer_strcpy (LastDir, NONULL(Homedir));
955                   goto bail;
956                 }
957               }
958             menu->current = 0;
959             menu->top = 0;
960             init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
961             break;
962           }
963         }
964         else if (op == OP_DESCEND_DIRECTORY)
965         {
966           mutt_error (_("%s is not a directory."), state.entry[menu->current].display_name);
967           break;
968         }
969
970         mutt_buffer_strcpy (f, state.entry[menu->current].full_path);
971
972         /* Fall through to OP_EXIT */
973
974       case OP_EXIT:
975
976         if (multiple)
977         {
978           char **tfiles;
979
980           if (menu->tagged)
981           {
982             *numfiles = menu->tagged;
983             tfiles = safe_calloc (*numfiles, sizeof (char *));
984             for (i = 0, j = 0; i < state.entrylen; i++)
985               if (state.entry[i].tagged)
986                 tfiles[j++] = safe_strdup (state.entry[i].full_path);
987             *files = tfiles;
988           }
989           else if ((mutt_b2s (f))[0]) /* no tagged entries. return selected entry */
990           {
991             *numfiles = 1;
992             tfiles = safe_calloc (*numfiles, sizeof (char *));
993             tfiles[0] = safe_strdup (mutt_b2s (f));
994             *files = tfiles;
995           }
996         }
997
998         destroy_state (&state);
999         goto bail;
1000
1001       case OP_BROWSER_TELL:
1002         if (state.entrylen)
1003           mutt_message("%s", state.entry[menu->current].full_path);
1004         break;
1005
1006 #ifdef USE_IMAP
1007       case OP_BROWSER_SUBSCRIBE:
1008         imap_subscribe (state.entry[menu->current].full_path, 1);
1009         break;
1010
1011       case OP_BROWSER_UNSUBSCRIBE:
1012         imap_subscribe (state.entry[menu->current].full_path, 0);
1013         break;
1014
1015       case OP_BROWSER_TOGGLE_LSUB:
1016         if (option (OPTIMAPLSUB))
1017           unset_option (OPTIMAPLSUB);
1018         else
1019           set_option (OPTIMAPLSUB);
1020
1021         mutt_unget_event (0, OP_CHECK_NEW);
1022         break;
1023
1024       case OP_CREATE_MAILBOX:
1025         if (!state.imap_browse)
1026         {
1027           mutt_error (_("Create is only supported for IMAP mailboxes"));
1028           break;
1029         }
1030
1031         if (!imap_mailbox_create (mutt_b2s (LastDir), defaultsel))
1032         {
1033           /* TODO: find a way to detect if the new folder would appear in
1034            *   this window, and insert it without starting over. */
1035           destroy_state (&state);
1036           init_state (&state, NULL);
1037           state.imap_browse = 1;
1038           imap_browse (mutt_b2s (LastDir), &state);
1039           browser_sort (&state);
1040           menu->data = state.entry;
1041           menu->current = 0;
1042           menu->top = 0;
1043           init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1044         }
1045         /* else leave error on screen */
1046         break;
1047
1048       case OP_RENAME_MAILBOX:
1049         if (!state.entry[menu->current].imap)
1050           mutt_error (_("Rename is only supported for IMAP mailboxes"));
1051         else
1052         {
1053           int nentry = menu->current;
1054
1055           if (imap_mailbox_rename (state.entry[nentry].full_path, defaultsel) >= 0)
1056           {
1057             destroy_state (&state);
1058             init_state (&state, NULL);
1059             state.imap_browse = 1;
1060             imap_browse (mutt_b2s (LastDir), &state);
1061             browser_sort (&state);
1062             menu->data = state.entry;
1063             menu->current = 0;
1064             menu->top = 0;
1065             init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1066           }
1067         }
1068         break;
1069
1070       case OP_DELETE_MAILBOX:
1071         if (!state.entry[menu->current].imap)
1072           mutt_error (_("Delete is only supported for IMAP mailboxes"));
1073         else
1074         {
1075           char msg[SHORT_STRING];
1076           IMAP_MBOX mx;
1077           int nentry = menu->current;
1078
1079           imap_parse_path (state.entry[nentry].full_path, &mx);
1080           if (!mx.mbox)
1081           {
1082             mutt_error _("Cannot delete root folder");
1083             break;
1084           }
1085           snprintf (msg, sizeof (msg), _("Really delete mailbox \"%s\"?"),
1086                     mx.mbox);
1087           if (mutt_yesorno (msg, MUTT_NO) == MUTT_YES)
1088           {
1089             if (!imap_delete_mailbox (Context, mx))
1090             {
1091               /* free the mailbox from the browser */
1092               FREE (&((state.entry)[nentry].display_name));
1093               FREE (&((state.entry)[nentry].full_path));
1094               /* and move all other entries up */
1095               if (nentry+1 < state.entrylen)
1096                 memmove (state.entry + nentry, state.entry + nentry + 1,
1097                          sizeof (struct folder_file) * (state.entrylen - (nentry+1)));
1098               memset (&state.entry[state.entrylen - 1], 0,
1099                       sizeof (struct folder_file));
1100               state.entrylen--;
1101               mutt_message _("Mailbox deleted.");
1102               mutt_buffer_clear (defaultsel);
1103               init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1104             }
1105             else
1106               mutt_error _("Mailbox deletion failed.");
1107           }
1108           else
1109             mutt_message _("Mailbox not deleted.");
1110           FREE (&mx.mbox);
1111         }
1112         break;
1113 #endif
1114
1115       case OP_CHANGE_DIRECTORY:
1116
1117         mutt_buffer_strcpy (buf, mutt_b2s (LastDir));
1118         mutt_buffer_clear (defaultsel);
1119 #ifdef USE_IMAP
1120         if (!state.imap_browse)
1121 #endif
1122         {
1123           /* add '/' at the end of the directory name if not already there */
1124           size_t len = mutt_buffer_len (LastDir);
1125           if (len && (mutt_b2s (LastDir)[len-1] != '/'))
1126             mutt_buffer_addch (buf, '/');
1127         }
1128
1129         /* buf comes from the buffer pool, so defaults to size LONG_STRING */
1130         if ((mutt_buffer_get_field (_("Chdir to: "), buf, MUTT_FILE) == 0) &&
1131             mutt_buffer_len (buf))
1132         {
1133           buffy = 0;
1134           mutt_buffer_expand_path (buf);
1135 #ifdef USE_IMAP
1136           if (mx_is_imap (mutt_b2s (buf)))
1137           {
1138             mutt_buffer_strcpy (LastDir, mutt_b2s (buf));
1139             destroy_state (&state);
1140             init_state (&state, NULL);
1141             state.imap_browse = 1;
1142             imap_browse (mutt_b2s (LastDir), &state);
1143             browser_sort (&state);
1144             menu->data = state.entry;
1145             menu->current = 0;
1146             menu->top = 0;
1147             init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1148           }
1149           else
1150 #endif
1151           {
1152             if (*(mutt_b2s (buf)) != '/')
1153             {
1154               /* in case dir is relative, make it relative to LastDir,
1155                * not current working dir */
1156               mutt_buffer_concat_path (tmp, mutt_b2s (LastDir), mutt_b2s (buf));
1157               mutt_buffer_strcpy (buf, mutt_b2s (tmp));
1158             }
1159             if (stat (mutt_b2s (buf), &st) == 0)
1160             {
1161               if (S_ISDIR (st.st_mode))
1162               {
1163                 destroy_state (&state);
1164                 if (examine_directory (menu, &state, mutt_b2s (buf), mutt_b2s (prefix)) == 0)
1165                   mutt_buffer_strcpy (LastDir, mutt_b2s (buf));
1166                 else
1167                 {
1168                   mutt_error _("Error scanning directory.");
1169                   if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
1170                   {
1171                     goto bail;
1172                   }
1173                 }
1174                 menu->current = 0;
1175                 menu->top = 0;
1176                 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1177               }
1178               else
1179                 mutt_error (_("%s is not a directory."), mutt_b2s (buf));
1180             }
1181             else
1182               mutt_perror (mutt_b2s (buf));
1183           }
1184         }
1185         break;
1186
1187       case OP_ENTER_MASK:
1188
1189         mutt_buffer_strcpy (buf, NONULL(Mask.pattern));
1190         /* buf comes from the buffer pool, so defaults to size LONG_STRING */
1191         if (mutt_buffer_get_field (_("File Mask: "), buf, 0) == 0)
1192         {
1193           regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t));
1194           const char *s = mutt_b2s (buf);
1195           int not = 0, err;
1196
1197           buffy = 0;
1198           /* assume that the user wants to see everything */
1199           if (!(mutt_buffer_len (buf)))
1200             mutt_buffer_strcpy (buf, ".");
1201           SKIPWS (s);
1202           if (*s == '!')
1203           {
1204             s++;
1205             SKIPWS (s);
1206             not = 1;
1207           }
1208
1209           if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0)
1210           {
1211             regerror (err, rx, buf->data, buf->dsize);
1212             mutt_buffer_fix_dptr (buf);
1213             FREE (&rx);
1214             mutt_error ("%s", mutt_b2s (buf));
1215           }
1216           else
1217           {
1218             mutt_str_replace (&Mask.pattern, mutt_b2s (buf));
1219             regfree (Mask.rx);
1220             FREE (&Mask.rx);
1221             Mask.rx = rx;
1222             Mask.not = not;
1223
1224             destroy_state (&state);
1225 #ifdef USE_IMAP
1226             if (state.imap_browse)
1227             {
1228               init_state (&state, NULL);
1229               state.imap_browse = 1;
1230               imap_browse (mutt_b2s (LastDir), &state);
1231               browser_sort (&state);
1232               menu->data = state.entry;
1233               init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1234             }
1235             else
1236 #endif
1237               if (examine_directory (menu, &state, mutt_b2s (LastDir), NULL) == 0)
1238                 init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1239               else
1240               {
1241                 mutt_error _("Error scanning directory.");
1242                 goto bail;
1243               }
1244             killPrefix = 0;
1245             if (!state.entrylen)
1246             {
1247               mutt_error _("No files match the file mask");
1248               break;
1249             }
1250           }
1251         }
1252         break;
1253
1254       case OP_SORT:
1255       case OP_SORT_REVERSE:
1256
1257       {
1258         int resort = 1;
1259         int reverse = (op == OP_SORT_REVERSE);
1260
1261         switch (mutt_multi_choice ((reverse) ?
1262             _("Reverse sort by (d)ate, (a)lpha, si(z)e, (c)ount, (u)nread, or do(n)'t sort? ") :
1263             _("Sort by (d)ate, (a)lpha, si(z)e, (c)ount, (u)nread, or do(n)'t sort? "),
1264             _("dazcun")))
1265         {
1266           case -1: /* abort */
1267             resort = 0;
1268             break;
1269
1270           case 1: /* (d)ate */
1271             BrowserSort = SORT_DATE;
1272             break;
1273
1274           case 2: /* (a)lpha */
1275             BrowserSort = SORT_SUBJECT;
1276             break;
1277
1278           case 3: /* si(z)e */
1279             BrowserSort = SORT_SIZE;
1280             break;
1281
1282           case 4: /* (c)ount */
1283             BrowserSort = SORT_COUNT;
1284             break;
1285
1286           case 5: /* (u)nread */
1287             BrowserSort = SORT_UNREAD;
1288             break;
1289
1290           case 6: /* do(n)'t sort */
1291             BrowserSort = SORT_ORDER;
1292             resort = 0;
1293             break;
1294         }
1295         if (resort)
1296         {
1297           BrowserSort |= reverse ? SORT_REVERSE : 0;
1298           browser_sort (&state);
1299           set_sticky_cursor (&state, menu, mutt_b2s (defaultsel));
1300           menu->redraw = REDRAW_FULL;
1301         }
1302         break;
1303       }
1304
1305       case OP_TOGGLE_MAILBOXES:
1306         buffy = 1 - buffy;
1307         menu->current = 0;
1308         /* fall through */
1309
1310       case OP_CHECK_NEW:
1311         destroy_state (&state);
1312         mutt_buffer_clear (prefix);
1313         killPrefix = 0;
1314
1315         if (buffy)
1316         {
1317           if (examine_mailboxes (menu, &state) == -1)
1318             goto bail;
1319         }
1320 #ifdef USE_IMAP
1321         else if (mx_is_imap (mutt_b2s (LastDir)))
1322         {
1323           init_state (&state, NULL);
1324           state.imap_browse = 1;
1325           imap_browse (mutt_b2s (LastDir), &state);
1326           browser_sort (&state);
1327           menu->data = state.entry;
1328         }
1329 #endif
1330         else if (examine_directory (menu, &state, mutt_b2s (LastDir), mutt_b2s (prefix)) == -1)
1331           goto bail;
1332         init_menu (&state, menu, title, sizeof (title), buffy, mutt_b2s (defaultsel));
1333         break;
1334
1335       case OP_BUFFY_LIST:
1336         mutt_buffy_list ();
1337         break;
1338
1339       case OP_BROWSER_NEW_FILE:
1340
1341         mutt_buffer_printf (buf, "%s/", mutt_b2s (LastDir));
1342         /* buf comes from the buffer pool, so defaults to size LONG_STRING */
1343         if (mutt_buffer_get_field (_("New file name: "), buf, MUTT_FILE) == 0)
1344         {
1345           mutt_buffer_strcpy (f, mutt_b2s (buf));
1346           destroy_state (&state);
1347           goto bail;
1348         }
1349         break;
1350
1351       case OP_BROWSER_VIEW_FILE:
1352         if (!state.entrylen)
1353         {
1354           mutt_error _("No files match the file mask");
1355           break;
1356         }
1357
1358 #ifdef USE_IMAP
1359         if (state.entry[menu->current].selectable)
1360         {
1361           mutt_buffer_strcpy (f, state.entry[menu->current].full_path);
1362           destroy_state (&state);
1363           goto bail;
1364         }
1365         else
1366 #endif
1367           if (S_ISDIR (state.entry[menu->current].mode) ||
1368               (S_ISLNK (state.entry[menu->current].mode) &&
1369                link_is_dir (state.entry[menu->current].full_path)))
1370           {
1371             mutt_error _("Can't view a directory");
1372             break;
1373           }
1374           else
1375           {
1376             BODY *b;
1377
1378             b = mutt_make_file_attach (state.entry[menu->current].full_path);
1379             if (b != NULL)
1380             {
1381               mutt_view_attachment (NULL, b, MUTT_REGULAR, NULL, NULL);
1382               mutt_free_body (&b);
1383               menu->redraw = REDRAW_FULL;
1384             }
1385             else
1386               mutt_error _("Error trying to view file");
1387           }
1388     }
1389   }
1390
1391 bail:
1392   mutt_buffer_pool_release (&buf);
1393   mutt_buffer_pool_release (&prefix);
1394   mutt_buffer_pool_release (&tmp);
1395   mutt_buffer_pool_release (&OldLastDir);
1396   mutt_buffer_pool_release (&defaultsel);
1397
1398   if (menu)
1399   {
1400     mutt_pop_current_menu (menu);
1401     mutt_menuDestroy (&menu);
1402   }
1403
1404   if (!folder)
1405     mutt_buffer_strcpy (LastDir, mutt_b2s (LastDirBackup));
1406
1407 }