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