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