]> git.llucax.com Git - software/mutt-debian.git/blob - browser.c
Move Mutt with NNTP support to mutt-nntp package
[software/mutt-debian.git] / browser.c
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.  */
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
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
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
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
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
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 *
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
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
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
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
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
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
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
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
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
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 }