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