1 /**
2  * @file
3  * File/Mailbox Browser Dialog
4  *
5  * @authors
6  * Copyright (C) 1996-2000,2007,2010,2013 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2020 R Primus <rprimus@gmail.com>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * @page neo_browser File/Mailbox Browser Dialog
26  *
27  * The File/Mailbox Browser Dialog lets the user select from a list of files or
28  * mailboxes.
29  *
30  * This is a @ref gui_simple
31  *
32  * ## Windows
33  *
34  * | Name           | Type           | See Also                  |
35  * | :------------- | :------------- | :------------------------ |
36  * | Browser Dialog | WT_DLG_BROWSER | mutt_buffer_select_file() |
37  *
38  * **Parent**
39  * - @ref gui_dialog
40  *
41  * **Children**
42  * - See: @ref gui_simple
43  *
44  * ## Data
45  * - #Menu
46  * - #Menu::mdata
47  * - #BrowserState
48  *
49  * The @ref gui_simple holds a Menu.  The Browser Dialog stores its data
50  * (#BrowserState) in Menu::mdata.
51  *
52  * ## Events
53  *
54  * Once constructed, it is controlled by the following events:
55  *
56  * | Event Type            | Handler                     |
57  * | :-------------------- | :-------------------------- |
58  * | #NT_CONFIG            | browser_config_observer() |
59  * | #NT_WINDOW            | browser_window_observer() |
60  *
61  * The Browser Dialog doesn't have any specific colours, so it doesn't need to
62  * support #NT_COLOR.
63  *
64  * The Browser Dialog does not implement MuttWindow::recalc() or MuttWindow::repaint().
65  *
66  * Some other events are handled by the @ref gui_simple.
67  */
68 
69 #include "config.h"
70 #include <dirent.h>
71 #include <errno.h>
72 #include <grp.h>
73 #include <limits.h>
74 #include <locale.h>
75 #include <pwd.h>
76 #include <stdbool.h>
77 #include <stdio.h>
78 #include <string.h>
79 #include <sys/stat.h>
80 #include "mutt/lib.h"
81 #include "config/lib.h"
82 #include "email/lib.h"
83 #include "core/lib.h"
84 #include "conn/lib.h"
85 #include "gui/lib.h"
86 #include "mutt.h"
87 #include "browser.h"
88 #include "attach/lib.h"
89 #include "menu/lib.h"
90 #include "question/lib.h"
91 #include "send/lib.h"
92 #include "format_flags.h"
93 #include "mutt_globals.h"
94 #include "mutt_mailbox.h"
95 #include "muttlib.h"
96 #include "mx.h"
97 #include "opcodes.h"
98 #include "options.h"
99 #ifdef USE_IMAP
100 #include "imap/lib.h"
101 #endif
102 #ifdef USE_NNTP
103 #include "nntp/lib.h"
104 #include "nntp/adata.h" // IWYU pragma: keep
105 #include "nntp/mdata.h" // IWYU pragma: keep
106 #endif
107 
108 /// Help Bar for the File/Dir/Mailbox browser dialog
109 static const struct Mapping FolderHelp[] = {
110   // clang-format off
111   { N_("Exit"),  OP_EXIT },
112   { N_("Chdir"), OP_CHANGE_DIRECTORY },
113   { N_("Goto"),  OP_BROWSER_GOTO_FOLDER },
114   { N_("Mask"),  OP_ENTER_MASK },
115   { N_("Help"),  OP_HELP },
116   { NULL, 0 },
117   // clang-format on
118 };
119 
120 #ifdef USE_NNTP
121 /// Help Bar for the NNTP Mailbox browser dialog
122 static const struct Mapping FolderNewsHelp[] = {
123   // clang-format off
124   { N_("Exit"),        OP_EXIT },
125   { N_("List"),        OP_TOGGLE_MAILBOXES },
126   { N_("Subscribe"),   OP_BROWSER_SUBSCRIBE },
127   { N_("Unsubscribe"), OP_BROWSER_UNSUBSCRIBE },
128   { N_("Catchup"),     OP_CATCHUP },
129   { N_("Mask"),        OP_ENTER_MASK },
130   { N_("Help"),        OP_HELP },
131   { NULL, 0 },
132   // clang-format on
133 };
134 #endif
135 
136 static struct Buffer LastDir = { 0 };
137 static struct Buffer LastDirBackup = { 0 };
138 
139 /**
140  * init_lastdir - Initialise the browser directories
141  *
142  * These keep track of where the browser used to be looking.
143  */
init_lastdir(void)144 static void init_lastdir(void)
145 {
146   static bool done = false;
147   if (!done)
148   {
149     mutt_buffer_alloc(&LastDir, PATH_MAX);
150     mutt_buffer_alloc(&LastDirBackup, PATH_MAX);
151     done = true;
152   }
153 }
154 
155 /**
156  * mutt_browser_cleanup - Clean up working Buffers
157  */
mutt_browser_cleanup(void)158 void mutt_browser_cleanup(void)
159 {
160   mutt_buffer_dealloc(&LastDir);
161   mutt_buffer_dealloc(&LastDirBackup);
162 }
163 
164 /**
165  * destroy_state - Free the BrowserState
166  * @param state State to free
167  *
168  * Frees up the memory allocated for the local-global variables.
169  */
destroy_state(struct BrowserState * state)170 static void destroy_state(struct BrowserState *state)
171 {
172   struct FolderFile *ff = NULL;
173   ARRAY_FOREACH(ff, &state->entry)
174   {
175     FREE(&ff->name);
176     FREE(&ff->desc);
177   }
178   ARRAY_FREE(&state->entry);
179 
180 #ifdef USE_IMAP
181   FREE(&state->folder);
182 #endif
183 }
184 
185 /**
186  * browser_compare_subject - Compare the subject of two browser entries - Implements ::sort_t - @ingroup sort_api
187  */
browser_compare_subject(const void * a,const void * b)188 static int browser_compare_subject(const void *a, const void *b)
189 {
190   const struct FolderFile *pa = (const struct FolderFile *) a;
191   const struct FolderFile *pb = (const struct FolderFile *) b;
192 
193   /* inbox should be sorted ahead of its siblings */
194   int r = mutt_inbox_cmp(pa->name, pb->name);
195   if (r == 0)
196     r = mutt_str_coll(pa->name, pb->name);
197   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
198   return (c_sort_browser & SORT_REVERSE) ? -r : r;
199 }
200 
201 /**
202  * browser_compare_order - Compare the order of creation of two browser entries - Implements ::sort_t - @ingroup sort_api
203  *
204  * @note This only affects browsing mailboxes and is a no-op for folders.
205  */
browser_compare_order(const void * a,const void * b)206 static int browser_compare_order(const void *a, const void *b)
207 {
208   const struct FolderFile *pa = (const struct FolderFile *) a;
209   const struct FolderFile *pb = (const struct FolderFile *) b;
210 
211   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
212   return ((c_sort_browser & SORT_REVERSE) ? -1 : 1) * (pa->gen - pb->gen);
213 }
214 
215 /**
216  * browser_compare_desc - Compare the descriptions of two browser entries - Implements ::sort_t - @ingroup sort_api
217  */
browser_compare_desc(const void * a,const void * b)218 static int browser_compare_desc(const void *a, const void *b)
219 {
220   const struct FolderFile *pa = (const struct FolderFile *) a;
221   const struct FolderFile *pb = (const struct FolderFile *) b;
222 
223   int r = mutt_str_coll(pa->desc, pb->desc);
224 
225   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
226   return (c_sort_browser & SORT_REVERSE) ? -r : r;
227 }
228 
229 /**
230  * browser_compare_date - Compare the date of two browser entries - Implements ::sort_t - @ingroup sort_api
231  */
browser_compare_date(const void * a,const void * b)232 static int browser_compare_date(const void *a, const void *b)
233 {
234   const struct FolderFile *pa = (const struct FolderFile *) a;
235   const struct FolderFile *pb = (const struct FolderFile *) b;
236 
237   int r = pa->mtime - pb->mtime;
238 
239   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
240   return (c_sort_browser & SORT_REVERSE) ? -r : r;
241 }
242 
243 /**
244  * browser_compare_size - Compare the size of two browser entries - Implements ::sort_t - @ingroup sort_api
245  */
browser_compare_size(const void * a,const void * b)246 static int browser_compare_size(const void *a, const void *b)
247 {
248   const struct FolderFile *pa = (const struct FolderFile *) a;
249   const struct FolderFile *pb = (const struct FolderFile *) b;
250 
251   int r = pa->size - pb->size;
252 
253   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
254   return (c_sort_browser & SORT_REVERSE) ? -r : r;
255 }
256 
257 /**
258  * browser_compare_count - Compare the message count of two browser entries - Implements ::sort_t - @ingroup sort_api
259  */
browser_compare_count(const void * a,const void * b)260 static int browser_compare_count(const void *a, const void *b)
261 {
262   const struct FolderFile *pa = (const struct FolderFile *) a;
263   const struct FolderFile *pb = (const struct FolderFile *) b;
264 
265   int r = 0;
266   if (pa->has_mailbox && pb->has_mailbox)
267     r = pa->msg_count - pb->msg_count;
268   else if (pa->has_mailbox)
269     r = -1;
270   else
271     r = 1;
272 
273   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
274   return (c_sort_browser & SORT_REVERSE) ? -r : r;
275 }
276 
277 /**
278  * browser_compare_count_new - Compare the new count of two browser entries - Implements ::sort_t - @ingroup sort_api
279  */
browser_compare_count_new(const void * a,const void * b)280 static int browser_compare_count_new(const void *a, const void *b)
281 {
282   const struct FolderFile *pa = (const struct FolderFile *) a;
283   const struct FolderFile *pb = (const struct FolderFile *) b;
284 
285   int r = 0;
286   if (pa->has_mailbox && pb->has_mailbox)
287     r = pa->msg_unread - pb->msg_unread;
288   else if (pa->has_mailbox)
289     r = -1;
290   else
291     r = 1;
292 
293   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
294   return (c_sort_browser & SORT_REVERSE) ? -r : r;
295 }
296 
297 /**
298  * browser_compare - Sort the items in the browser - Implements ::sort_t - @ingroup sort_api
299  *
300  * Wild compare function that calls the others. It's useful because it provides
301  * a way to tell "../" is always on the top of the list, independently of the
302  * sort method.
303  */
browser_compare(const void * a,const void * b)304 static int browser_compare(const void *a, const void *b)
305 {
306   const struct FolderFile *pa = (const struct FolderFile *) a;
307   const struct FolderFile *pb = (const struct FolderFile *) b;
308 
309   if ((mutt_str_coll(pa->desc, "../") == 0) || (mutt_str_coll(pa->desc, "..") == 0))
310     return -1;
311   if ((mutt_str_coll(pb->desc, "../") == 0) || (mutt_str_coll(pb->desc, "..") == 0))
312     return 1;
313 
314   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
315   switch (c_sort_browser & SORT_MASK)
316   {
317     case SORT_COUNT:
318       return browser_compare_count(a, b);
319     case SORT_DATE:
320       return browser_compare_date(a, b);
321     case SORT_DESC:
322       return browser_compare_desc(a, b);
323     case SORT_SIZE:
324       return browser_compare_size(a, b);
325     case SORT_UNREAD:
326       return browser_compare_count_new(a, b);
327     case SORT_SUBJECT:
328       return browser_compare_subject(a, b);
329     default:
330     case SORT_ORDER:
331       return browser_compare_order(a, b);
332   }
333 }
334 
335 /**
336  * browser_sort - Sort the entries in the browser
337  * @param state Browser state
338  *
339  * Call to qsort using browser_compare function.
340  * Some specific sort methods are not used via NNTP.
341  */
browser_sort(struct BrowserState * state)342 static void browser_sort(struct BrowserState *state)
343 {
344   const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
345   switch (c_sort_browser & SORT_MASK)
346   {
347 #ifdef USE_NNTP
348     case SORT_SIZE:
349     case SORT_DATE:
350       if (OptNews)
351         return;
352 #endif
353     default:
354       break;
355   }
356 
357   ARRAY_SORT(&state->entry, browser_compare);
358 }
359 
360 /**
361  * link_is_dir - Does this symlink point to a directory?
362  * @param folder Folder
363  * @param path   Link name
364  * @retval true  Links to a directory
365  * @retval false Otherwise
366  */
link_is_dir(const char * folder,const char * path)367 static bool link_is_dir(const char *folder, const char *path)
368 {
369   struct stat st = { 0 };
370   bool retval = false;
371 
372   struct Buffer *fullpath = mutt_buffer_pool_get();
373   mutt_buffer_concat_path(fullpath, folder, path);
374 
375   if (stat(mutt_buffer_string(fullpath), &st) == 0)
376     retval = S_ISDIR(st.st_mode);
377 
378   mutt_buffer_pool_release(&fullpath);
379 
380   return retval;
381 }
382 
383 /**
384  * folder_format_str - Format a string for the folder browser - Implements ::format_t - @ingroup expando_api
385  *
386  * | Expando | Description
387  * |:--------|:--------------------------------------------------------
388  * | \%C     | Current file number
389  * | \%d     | Date/time folder was last modified
390  * | \%D     | Date/time folder was last modified using `$date_format.`
391  * | \%F     | File permissions
392  * | \%f     | Filename (with suffix `/`, `@` or `*`)
393  * | \%g     | Group name (or numeric gid, if missing)
394  * | \%i     | Description of the folder
395  * | \%l     | Number of hard links
396  * | \%m     | Number of messages in the mailbox
397  * | \%N     | N if mailbox has new mail, blank otherwise
398  * | \%n     | Number of unread messages in the mailbox
399  * | \%s     | Size in bytes
400  * | \%t     | `*` if the file is tagged, blank otherwise
401  * | \%u     | Owner name (or numeric uid, if missing)
402  */
folder_format_str(char * buf,size_t buflen,size_t col,int cols,char op,const char * src,const char * prec,const char * if_str,const char * else_str,intptr_t data,MuttFormatFlags flags)403 static const char *folder_format_str(char *buf, size_t buflen, size_t col, int cols,
404                                      char op, const char *src, const char *prec,
405                                      const char *if_str, const char *else_str,
406                                      intptr_t data, MuttFormatFlags flags)
407 {
408   char fn[128], fmt[128];
409   struct Folder *folder = (struct Folder *) data;
410   bool optional = (flags & MUTT_FORMAT_OPTIONAL);
411 
412   switch (op)
413   {
414     case 'C':
415       snprintf(fmt, sizeof(fmt), "%%%sd", prec);
416       snprintf(buf, buflen, fmt, folder->num + 1);
417       break;
418 
419     case 'd':
420     case 'D':
421       if (folder->ff->local)
422       {
423         bool do_locales = true;
424 
425         const char *t_fmt = NULL;
426         if (op == 'D')
427         {
428           const char *const c_date_format =
429               cs_subset_string(NeoMutt->sub, "date_format");
430           t_fmt = NONULL(c_date_format);
431           if (*t_fmt == '!')
432           {
433             t_fmt++;
434             do_locales = false;
435           }
436         }
437         else
438         {
439           static const time_t one_year = 31536000;
440           t_fmt = ((mutt_date_epoch() - folder->ff->mtime) < one_year) ?
441                       "%b %d %H:%M" :
442                       "%b %d  %Y";
443         }
444 
445         if (!do_locales)
446           setlocale(LC_TIME, "C");
447         char date[128];
448         mutt_date_localtime_format(date, sizeof(date), t_fmt, folder->ff->mtime);
449         if (!do_locales)
450           setlocale(LC_TIME, "");
451 
452         mutt_format_s(buf, buflen, prec, date);
453       }
454       else
455         mutt_format_s(buf, buflen, prec, "");
456       break;
457 
458     case 'f':
459     {
460       char *s = NULL;
461 
462       s = NONULL(folder->ff->name);
463 
464       snprintf(fn, sizeof(fn), "%s%s", s,
465                folder->ff->local ?
466                    (S_ISLNK(folder->ff->mode) ?
467                         "@" :
468                         (S_ISDIR(folder->ff->mode) ?
469                              "/" :
470                              (((folder->ff->mode & S_IXUSR) != 0) ? "*" : ""))) :
471                    "");
472 
473       mutt_format_s(buf, buflen, prec, fn);
474       break;
475     }
476     case 'F':
477     {
478       if (folder->ff->local)
479       {
480         char permission[11];
481         snprintf(permission, sizeof(permission), "%c%c%c%c%c%c%c%c%c%c",
482                  S_ISDIR(folder->ff->mode) ? 'd' : (S_ISLNK(folder->ff->mode) ? 'l' : '-'),
483                  ((folder->ff->mode & S_IRUSR) != 0) ? 'r' : '-',
484                  ((folder->ff->mode & S_IWUSR) != 0) ? 'w' : '-',
485                  ((folder->ff->mode & S_ISUID) != 0) ? 's' :
486                  ((folder->ff->mode & S_IXUSR) != 0) ? 'x' :
487                                                        '-',
488                  ((folder->ff->mode & S_IRGRP) != 0) ? 'r' : '-',
489                  ((folder->ff->mode & S_IWGRP) != 0) ? 'w' : '-',
490                  ((folder->ff->mode & S_ISGID) != 0) ? 's' :
491                  ((folder->ff->mode & S_IXGRP) != 0) ? 'x' :
492                                                        '-',
493                  ((folder->ff->mode & S_IROTH) != 0) ? 'r' : '-',
494                  ((folder->ff->mode & S_IWOTH) != 0) ? 'w' : '-',
495                  ((folder->ff->mode & S_ISVTX) != 0) ? 't' :
496                  ((folder->ff->mode & S_IXOTH) != 0) ? 'x' :
497                                                        '-');
498         mutt_format_s(buf, buflen, prec, permission);
499       }
500 #ifdef USE_IMAP
501       else if (folder->ff->imap)
502       {
503         char permission[11];
504         /* mark folders with subfolders AND mail */
505         snprintf(permission, sizeof(permission), "IMAP %c",
506                  (folder->ff->inferiors && folder->ff->selectable) ? '+' : ' ');
507         mutt_format_s(buf, buflen, prec, permission);
508       }
509 #endif
510       else
511         mutt_format_s(buf, buflen, prec, "");
512       break;
513     }
514 
515     case 'g':
516       if (folder->ff->local)
517       {
518         struct group *gr = getgrgid(folder->ff->gid);
519         if (gr)
520           mutt_format_s(buf, buflen, prec, gr->gr_name);
521         else
522         {
523           snprintf(fmt, sizeof(fmt), "%%%sld", prec);
524           snprintf(buf, buflen, fmt, folder->ff->gid);
525         }
526       }
527       else
528         mutt_format_s(buf, buflen, prec, "");
529       break;
530 
531     case 'i':
532     {
533       char *s = NULL;
534       if (folder->ff->desc)
535         s = folder->ff->desc;
536       else
537         s = folder->ff->name;
538 
539       snprintf(fn, sizeof(fn), "%s%s", s,
540                folder->ff->local ?
541                    (S_ISLNK(folder->ff->mode) ?
542                         "@" :
543                         (S_ISDIR(folder->ff->mode) ?
544                              "/" :
545                              (((folder->ff->mode & S_IXUSR) != 0) ? "*" : ""))) :
546                    "");
547 
548       mutt_format_s(buf, buflen, prec, fn);
549       break;
550     }
551 
552     case 'l':
553       if (folder->ff->local)
554       {
555         snprintf(fmt, sizeof(fmt), "%%%sd", prec);
556         snprintf(buf, buflen, fmt, folder->ff->nlink);
557       }
558       else
559         mutt_format_s(buf, buflen, prec, "");
560       break;
561 
562     case 'm':
563       if (!optional)
564       {
565         if (folder->ff->has_mailbox)
566         {
567           snprintf(fmt, sizeof(fmt), "%%%sd", prec);
568           snprintf(buf, buflen, fmt, folder->ff->msg_count);
569         }
570         else
571           mutt_format_s(buf, buflen, prec, "");
572       }
573       else if (folder->ff->msg_count == 0)
574         optional = false;
575       break;
576 
577     case 'N':
578       snprintf(fmt, sizeof(fmt), "%%%sc", prec);
579       snprintf(buf, buflen, fmt, folder->ff->has_new_mail ? 'N' : ' ');
580       break;
581 
582     case 'n':
583       if (!optional)
584       {
585         if (folder->ff->has_mailbox)
586         {
587           snprintf(fmt, sizeof(fmt), "%%%sd", prec);
588           snprintf(buf, buflen, fmt, folder->ff->msg_unread);
589         }
590         else
591           mutt_format_s(buf, buflen, prec, "");
592       }
593       else if (folder->ff->msg_unread == 0)
594         optional = false;
595       break;
596 
597     case 's':
598       if (folder->ff->local)
599       {
600         mutt_str_pretty_size(fn, sizeof(fn), folder->ff->size);
601         snprintf(fmt, sizeof(fmt), "%%%ss", prec);
602         snprintf(buf, buflen, fmt, fn);
603       }
604       else
605         mutt_format_s(buf, buflen, prec, "");
606       break;
607 
608     case 't':
609       snprintf(fmt, sizeof(fmt), "%%%sc", prec);
610       snprintf(buf, buflen, fmt, folder->ff->tagged ? '*' : ' ');
611       break;
612 
613     case 'u':
614       if (folder->ff->local)
615       {
616         struct passwd *pw = getpwuid(folder->ff->uid);
617         if (pw)
618           mutt_format_s(buf, buflen, prec, pw->pw_name);
619         else
620         {
621           snprintf(fmt, sizeof(fmt), "%%%sld", prec);
622           snprintf(buf, buflen, fmt, folder->ff->uid);
623         }
624       }
625       else
626         mutt_format_s(buf, buflen, prec, "");
627       break;
628 
629     default:
630       snprintf(fmt, sizeof(fmt), "%%%sc", prec);
631       snprintf(buf, buflen, fmt, op);
632       break;
633   }
634 
635   if (optional)
636   {
637     mutt_expando_format(buf, buflen, col, cols, if_str, folder_format_str, data,
638                         MUTT_FORMAT_NO_FLAGS);
639   }
640   else if (flags & MUTT_FORMAT_OPTIONAL)
641   {
642     mutt_expando_format(buf, buflen, col, cols, else_str, folder_format_str,
643                         data, MUTT_FORMAT_NO_FLAGS);
644   }
645 
646   /* We return the format string, unchanged */
647   return src;
648 }
649 
650 /**
651  * add_folder - Add a folder to the browser list
652  * @param menu  Menu to use
653  * @param state Browser state
654  * @param name  Name of folder
655  * @param desc  Description of folder
656  * @param st    stat info for the folder
657  * @param m     Mailbox
658  * @param data  Data to associate with the folder
659  */
add_folder(struct Menu * menu,struct BrowserState * state,const char * name,const char * desc,const struct stat * st,struct Mailbox * m,void * data)660 static void add_folder(struct Menu *menu, struct BrowserState *state,
661                        const char *name, const char *desc,
662                        const struct stat *st, struct Mailbox *m, void *data)
663 {
664   if ((!menu || state->is_mailbox_list) && m && (m->flags & MB_HIDDEN))
665   {
666     return;
667   }
668 
669   struct FolderFile ff = { 0 };
670 
671   if (st)
672   {
673     ff.mode = st->st_mode;
674     ff.mtime = st->st_mtime;
675     ff.size = st->st_size;
676     ff.gid = st->st_gid;
677     ff.uid = st->st_uid;
678     ff.nlink = st->st_nlink;
679     ff.local = true;
680   }
681   else
682     ff.local = false;
683 
684   if (m)
685   {
686     ff.has_mailbox = true;
687     ff.gen = m->gen;
688     ff.has_new_mail = m->has_new;
689     ff.msg_count = m->msg_count;
690     ff.msg_unread = m->msg_unread;
691   }
692 
693   ff.name = mutt_str_dup(name);
694   ff.desc = mutt_str_dup(desc ? desc : name);
695 #ifdef USE_IMAP
696   ff.imap = false;
697 #endif
698 #ifdef USE_NNTP
699   if (OptNews)
700     ff.nd = data;
701 #endif
702 
703   ARRAY_ADD(&state->entry, ff);
704 }
705 
706 /**
707  * init_state - Initialise a browser state
708  * @param state BrowserState to initialise
709  * @param menu  Current menu
710  */
init_state(struct BrowserState * state,struct Menu * menu)711 static void init_state(struct BrowserState *state, struct Menu *menu)
712 {
713   ARRAY_INIT(&state->entry);
714   ARRAY_RESERVE(&state->entry, 256);
715 #ifdef USE_IMAP
716   state->imap_browse = false;
717 #endif
718   if (menu)
719     menu->mdata = &state->entry;
720 }
721 
722 /**
723  * examine_directory - Get list of all files/newsgroups with mask
724  * @param m      Mailbox
725  * @param menu   Current Menu
726  * @param state  State of browser
727  * @param d      Directory
728  * @param prefix Files/newsgroups must match this prefix
729  * @retval  0 Success
730  * @retval -1 Error
731  */
examine_directory(struct Mailbox * m,struct Menu * menu,struct BrowserState * state,const char * d,const char * prefix)732 static int examine_directory(struct Mailbox *m, struct Menu *menu,
733                              struct BrowserState *state, const char *d, const char *prefix)
734 {
735   int rc = -1;
736   struct Buffer *buf = mutt_buffer_pool_get();
737 #ifdef USE_NNTP
738   if (OptNews)
739   {
740     struct NntpAccountData *adata = CurrentNewsSrv;
741 
742     init_state(state, menu);
743 
744     for (unsigned int i = 0; i < adata->groups_num; i++)
745     {
746       struct NntpMboxData *mdata = adata->groups_list[i];
747       if (!mdata)
748         continue;
749       if (prefix && *prefix && !mutt_str_startswith(mdata->group, prefix))
750         continue;
751       const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
752       if (!mutt_regex_match(c_mask, mdata->group))
753       {
754         continue;
755       }
756       add_folder(menu, state, mdata->group, NULL, NULL, NULL, mdata);
757     }
758   }
759   else
760 #endif /* USE_NNTP */
761   {
762     struct stat st = { 0 };
763     DIR *dp = NULL;
764     struct dirent *de = NULL;
765 
766     while (stat(d, &st) == -1)
767     {
768       if (errno == ENOENT)
769       {
770         /* The last used directory is deleted, try to use the parent dir. */
771         char *c = strrchr(d, '/');
772 
773         if (c && (c > d))
774         {
775           *c = '\0';
776           continue;
777         }
778       }
779       mutt_perror(d);
780       goto ed_out;
781     }
782 
783     if (!S_ISDIR(st.st_mode))
784     {
785       mutt_error(_("%s is not a directory"), d);
786       goto ed_out;
787     }
788 
789     if (m)
790       mutt_mailbox_check(m, 0);
791 
792     dp = opendir(d);
793     if (!dp)
794     {
795       mutt_perror(d);
796       goto ed_out;
797     }
798 
799     init_state(state, menu);
800 
801     struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
802     neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
803     while ((de = readdir(dp)))
804     {
805       if (mutt_str_equal(de->d_name, "."))
806         continue; /* we don't need . */
807 
808       if (prefix && *prefix && !mutt_str_startswith(de->d_name, prefix))
809       {
810         continue;
811       }
812       const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
813       if (!mutt_regex_match(c_mask, de->d_name))
814       {
815         continue;
816       }
817 
818       mutt_buffer_concat_path(buf, d, de->d_name);
819       if (lstat(mutt_buffer_string(buf), &st) == -1)
820         continue;
821 
822       /* No size for directories or symlinks */
823       if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))
824         st.st_size = 0;
825       else if (!S_ISREG(st.st_mode))
826         continue;
827 
828       struct MailboxNode *np = NULL;
829       STAILQ_FOREACH(np, &ml, entries)
830       {
831         if (mutt_str_equal(mutt_buffer_string(buf), mailbox_path(np->mailbox)))
832           break;
833       }
834 
835       if (np && m && mutt_str_equal(np->mailbox->realpath, m->realpath))
836       {
837         np->mailbox->msg_count = m->msg_count;
838         np->mailbox->msg_unread = m->msg_unread;
839       }
840       add_folder(menu, state, de->d_name, NULL, &st, np ? np->mailbox : NULL, NULL);
841     }
842     neomutt_mailboxlist_clear(&ml);
843     closedir(dp);
844   }
845   browser_sort(state);
846   rc = 0;
847 ed_out:
848   mutt_buffer_pool_release(&buf);
849   return rc;
850 }
851 
852 /**
853  * examine_mailboxes - Get list of mailboxes/subscribed newsgroups
854  * @param m     Mailbox
855  * @param menu  Current menu
856  * @param state State of browser
857  * @retval  0 Success
858  * @retval -1 Error
859  */
examine_mailboxes(struct Mailbox * m,struct Menu * menu,struct BrowserState * state)860 static int examine_mailboxes(struct Mailbox *m, struct Menu *menu, struct BrowserState *state)
861 {
862   struct stat st = { 0 };
863   struct Buffer *md = NULL;
864   struct Buffer *mailbox = NULL;
865 
866 #ifdef USE_NNTP
867   if (OptNews)
868   {
869     struct NntpAccountData *adata = CurrentNewsSrv;
870 
871     init_state(state, menu);
872 
873     for (unsigned int i = 0; i < adata->groups_num; i++)
874     {
875       const bool c_show_only_unread =
876           cs_subset_bool(NeoMutt->sub, "show_only_unread");
877       struct NntpMboxData *mdata = adata->groups_list[i];
878       if (mdata && (mdata->has_new_mail ||
879                     (mdata->subscribed && (mdata->unread || !c_show_only_unread))))
880       {
881         add_folder(menu, state, mdata->group, NULL, NULL, NULL, mdata);
882       }
883     }
884   }
885   else
886 #endif
887   {
888     init_state(state, menu);
889 
890     if (TAILQ_EMPTY(&NeoMutt->accounts))
891       return -1;
892     mailbox = mutt_buffer_pool_get();
893     md = mutt_buffer_pool_get();
894 
895     mutt_mailbox_check(m, 0);
896 
897     struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
898     neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
899     struct MailboxNode *np = NULL;
900     STAILQ_FOREACH(np, &ml, entries)
901     {
902       if (!np->mailbox)
903         continue;
904 
905       if (m && mutt_str_equal(np->mailbox->realpath, m->realpath))
906       {
907         np->mailbox->msg_count = m->msg_count;
908         np->mailbox->msg_unread = m->msg_unread;
909       }
910 
911       mutt_buffer_strcpy(mailbox, mailbox_path(np->mailbox));
912       const bool c_browser_abbreviate_mailboxes =
913           cs_subset_bool(NeoMutt->sub, "browser_abbreviate_mailboxes");
914       if (c_browser_abbreviate_mailboxes)
915         mutt_buffer_pretty_mailbox(mailbox);
916 
917       switch (np->mailbox->type)
918       {
919         case MUTT_IMAP:
920         case MUTT_POP:
921           add_folder(menu, state, mutt_buffer_string(mailbox),
922                      np->mailbox->name, NULL, np->mailbox, NULL);
923           continue;
924         case MUTT_NOTMUCH:
925         case MUTT_NNTP:
926           add_folder(menu, state, mailbox_path(np->mailbox), np->mailbox->name,
927                      NULL, np->mailbox, NULL);
928           continue;
929         default: /* Continue */
930           break;
931       }
932 
933       if (lstat(mailbox_path(np->mailbox), &st) == -1)
934         continue;
935 
936       if ((!S_ISREG(st.st_mode)) && (!S_ISDIR(st.st_mode)) && (!S_ISLNK(st.st_mode)))
937         continue;
938 
939       if (np->mailbox->type == MUTT_MAILDIR)
940       {
941         struct stat st2 = { 0 };
942 
943         mutt_buffer_printf(md, "%s/new", mailbox_path(np->mailbox));
944         if (stat(mutt_buffer_string(md), &st) < 0)
945           st.st_mtime = 0;
946         mutt_buffer_printf(md, "%s/cur", mailbox_path(np->mailbox));
947         if (stat(mutt_buffer_string(md), &st2) < 0)
948           st2.st_mtime = 0;
949         if (st2.st_mtime > st.st_mtime)
950           st.st_mtime = st2.st_mtime;
951       }
952 
953       add_folder(menu, state, mutt_buffer_string(mailbox), np->mailbox->name,
954                  &st, np->mailbox, NULL);
955     }
956     neomutt_mailboxlist_clear(&ml);
957   }
958   browser_sort(state);
959 
960   mutt_buffer_pool_release(&mailbox);
961   mutt_buffer_pool_release(&md);
962   return 0;
963 }
964 
965 /**
966  * select_file_search - Menu search callback for matching files - Implements Menu::search() - @ingroup menu_search
967  */
select_file_search(struct Menu * menu,regex_t * rx,int line)968 static int select_file_search(struct Menu *menu, regex_t *rx, int line)
969 {
970   struct BrowserStateEntry *entry = menu->mdata;
971 #ifdef USE_NNTP
972   if (OptNews)
973     return regexec(rx, ARRAY_GET(entry, line)->desc, 0, NULL, 0);
974 #endif
975   struct FolderFile *ff = ARRAY_GET(entry, line);
976   char *search_on = ff->desc ? ff->desc : ff->name;
977 
978   return regexec(rx, search_on, 0, NULL, 0);
979 }
980 
981 /**
982  * folder_make_entry - Format a menu item for the folder browser - Implements Menu::make_entry() - @ingroup menu_make_entry
983  */
folder_make_entry(struct Menu * menu,char * buf,size_t buflen,int line)984 static void folder_make_entry(struct Menu *menu, char *buf, size_t buflen, int line)
985 {
986   struct BrowserStateEntry *entry = menu->mdata;
987   struct Folder folder = {
988     .ff = ARRAY_GET(entry, line),
989     .num = line,
990   };
991 
992 #ifdef USE_NNTP
993   if (OptNews)
994   {
995     const char *const c_group_index_format =
996         cs_subset_string(NeoMutt->sub, "group_index_format");
997     mutt_expando_format(buf, buflen, 0, menu->win->state.cols,
998                         NONULL(c_group_index_format), group_index_format_str,
999                         (intptr_t) &folder, MUTT_FORMAT_ARROWCURSOR);
1000   }
1001   else
1002 #endif
1003   {
1004     const char *const c_folder_format =
1005         cs_subset_string(NeoMutt->sub, "folder_format");
1006     mutt_expando_format(buf, buflen, 0, menu->win->state.cols, NONULL(c_folder_format),
1007                         folder_format_str, (intptr_t) &folder, MUTT_FORMAT_ARROWCURSOR);
1008   }
1009 }
1010 
1011 /**
1012  * browser_highlight_default - Decide which browser item should be highlighted
1013  * @param state Browser state
1014  * @param menu  Current Menu
1015  *
1016  * This function takes a menu and a state and defines the current entry that
1017  * should be highlighted.
1018  */
browser_highlight_default(struct BrowserState * state,struct Menu * menu)1019 static void browser_highlight_default(struct BrowserState *state, struct Menu *menu)
1020 {
1021   menu->top = 0;
1022   /* Reset menu position to 1.
1023    * We do not risk overflow as the init_menu function changes
1024    * current if it is bigger than state->entrylen.  */
1025   if (!ARRAY_EMPTY(&state->entry) &&
1026       (mutt_str_equal(ARRAY_FIRST(&state->entry)->desc, "..") ||
1027        mutt_str_equal(ARRAY_FIRST(&state->entry)->desc, "../")))
1028   {
1029     /* Skip the first entry, unless there's only one entry. */
1030     menu_set_index(menu, (menu->max > 1));
1031   }
1032   else
1033   {
1034     menu_set_index(menu, 0);
1035   }
1036 }
1037 
1038 /**
1039  * init_menu - Set up a new menu
1040  * @param state    Browser state
1041  * @param menu     Current menu
1042  * @param m        Mailbox
1043  * @param sbar     Status bar
1044  */
init_menu(struct BrowserState * state,struct Menu * menu,struct Mailbox * m,struct MuttWindow * sbar)1045 static void init_menu(struct BrowserState *state, struct Menu *menu,
1046                       struct Mailbox *m, struct MuttWindow *sbar)
1047 {
1048   char title[256] = { 0 };
1049   menu->max = ARRAY_SIZE(&state->entry);
1050 
1051   int index = menu_get_index(menu);
1052   if (index >= menu->max)
1053     menu_set_index(menu, menu->max - 1);
1054   if (index < 0)
1055     menu_set_index(menu, 0);
1056   if (menu->top > index)
1057     menu->top = 0;
1058 
1059   menu->tagged = 0;
1060 
1061 #ifdef USE_NNTP
1062   if (OptNews)
1063   {
1064     if (state->is_mailbox_list)
1065       snprintf(title, sizeof(title), _("Subscribed newsgroups"));
1066     else
1067     {
1068       snprintf(title, sizeof(title), _("Newsgroups on server [%s]"),
1069                CurrentNewsSrv->conn->account.host);
1070     }
1071   }
1072   else
1073 #endif
1074   {
1075     if (state->is_mailbox_list)
1076     {
1077       snprintf(title, sizeof(title), _("Mailboxes [%d]"), mutt_mailbox_check(m, 0));
1078     }
1079     else
1080     {
1081       struct Buffer *path = mutt_buffer_pool_get();
1082       mutt_buffer_copy(path, &LastDir);
1083       mutt_buffer_pretty_mailbox(path);
1084       const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
1085 #ifdef USE_IMAP
1086       const bool c_imap_list_subscribed =
1087           cs_subset_bool(NeoMutt->sub, "imap_list_subscribed");
1088       if (state->imap_browse && c_imap_list_subscribed)
1089       {
1090         snprintf(title, sizeof(title), _("Subscribed [%s], File mask: %s"),
1091                  mutt_buffer_string(path), NONULL(c_mask ? c_mask->pattern : NULL));
1092       }
1093       else
1094 #endif
1095       {
1096         snprintf(title, sizeof(title), _("Directory [%s], File mask: %s"),
1097                  mutt_buffer_string(path), NONULL(c_mask ? c_mask->pattern : NULL));
1098       }
1099       mutt_buffer_pool_release(&path);
1100     }
1101   }
1102   sbar_set_title(sbar, title);
1103 
1104   /* Browser tracking feature.
1105    * The goal is to highlight the good directory if LastDir is the parent dir
1106    * of LastDirBackup (this occurs mostly when one hit "../"). It should also work
1107    * properly when the user is in examine_mailboxes-mode.  */
1108   if (mutt_str_startswith(mutt_buffer_string(&LastDirBackup), mutt_buffer_string(&LastDir)))
1109   {
1110     char target_dir[PATH_MAX] = { 0 };
1111 
1112 #ifdef USE_IMAP
1113     /* Check what kind of dir LastDirBackup is. */
1114     if (imap_path_probe(mutt_buffer_string(&LastDirBackup), NULL) == MUTT_IMAP)
1115     {
1116       mutt_str_copy(target_dir, mutt_buffer_string(&LastDirBackup), sizeof(target_dir));
1117       imap_clean_path(target_dir, sizeof(target_dir));
1118     }
1119     else
1120 #endif
1121       mutt_str_copy(target_dir, strrchr(mutt_buffer_string(&LastDirBackup), '/') + 1,
1122                     sizeof(target_dir));
1123 
1124     /* If we get here, it means that LastDir is the parent directory of
1125      * LastDirBackup.  I.e., we're returning from a subdirectory, and we want
1126      * to position the cursor on the directory we're returning from. */
1127     bool matched = false;
1128     struct FolderFile *ff = NULL;
1129     ARRAY_FOREACH(ff, &state->entry)
1130     {
1131       if (mutt_str_equal(ff->name, target_dir))
1132       {
1133         menu_set_index(menu, ARRAY_FOREACH_IDX);
1134         matched = true;
1135         break;
1136       }
1137     }
1138     if (!matched)
1139       browser_highlight_default(state, menu);
1140   }
1141   else
1142     browser_highlight_default(state, menu);
1143 
1144   menu_queue_redraw(menu, MENU_REDRAW_FULL);
1145 }
1146 
1147 /**
1148  * file_tag - Tag an entry in the menu - Implements Menu::tag() - @ingroup menu_tag
1149  */
file_tag(struct Menu * menu,int sel,int act)1150 static int file_tag(struct Menu *menu, int sel, int act)
1151 {
1152   struct BrowserStateEntry *entry = menu->mdata;
1153   struct FolderFile *ff = ARRAY_GET(entry, sel);
1154   if (S_ISDIR(ff->mode) ||
1155       (S_ISLNK(ff->mode) && link_is_dir(mutt_buffer_string(&LastDir), ff->name)))
1156   {
1157     mutt_error(_("Can't attach a directory"));
1158     return 0;
1159   }
1160 
1161   bool ot = ff->tagged;
1162   ff->tagged = ((act >= 0) ? act : !ff->tagged);
1163 
1164   return ff->tagged - ot;
1165 }
1166 
1167 /**
1168  * browser_config_observer - Notification that a Config Variable has changed - Implements ::observer_t - @ingroup observer_api
1169  */
browser_config_observer(struct NotifyCallback * nc)1170 static int browser_config_observer(struct NotifyCallback *nc)
1171 {
1172   if ((nc->event_type != NT_CONFIG) || !nc->global_data || !nc->event_data)
1173     return -1;
1174 
1175   struct EventConfig *ev_c = nc->event_data;
1176 
1177   if (!mutt_str_equal(ev_c->name, "browser_abbreviate_mailboxes") &&
1178       !mutt_str_equal(ev_c->name, "date_format") && !mutt_str_equal(ev_c->name, "folder") &&
1179       !mutt_str_equal(ev_c->name, "folder_format") &&
1180       !mutt_str_equal(ev_c->name, "group_index_format") &&
1181       !mutt_str_equal(ev_c->name, "sort_browser"))
1182   {
1183     return 0;
1184   }
1185 
1186   struct Menu *menu = nc->global_data;
1187   menu_queue_redraw(menu, MENU_REDRAW_FULL);
1188   mutt_debug(LL_DEBUG5, "config done, request WA_RECALC, MENU_REDRAW_FULL\n");
1189 
1190   return 0;
1191 }
1192 
1193 /**
1194  * browser_window_observer - Notification that a Window has changed - Implements ::observer_t - @ingroup observer_api
1195  *
1196  * This function is triggered by changes to the windows.
1197  *
1198  * - Delete (this window): clean up the resources held by the Help Bar
1199  */
browser_window_observer(struct NotifyCallback * nc)1200 static int browser_window_observer(struct NotifyCallback *nc)
1201 {
1202   if ((nc->event_type != NT_WINDOW) || !nc->global_data || !nc->event_data)
1203     return -1;
1204 
1205   if (nc->event_subtype != NT_WINDOW_DELETE)
1206     return 0;
1207 
1208   struct MuttWindow *win_menu = nc->global_data;
1209   struct EventWindow *ev_w = nc->event_data;
1210   if (ev_w->win != win_menu)
1211     return 0;
1212 
1213   struct Menu *menu = win_menu->wdata;
1214 
1215   notify_observer_remove(NeoMutt->notify, browser_config_observer, menu);
1216   notify_observer_remove(win_menu->notify, browser_window_observer, win_menu);
1217 
1218   mutt_debug(LL_DEBUG5, "window delete done\n");
1219   return 0;
1220 }
1221 
1222 /**
1223  * mutt_browser_select_dir - Remember the last directory selected
1224  * @param f Directory name to save
1225  *
1226  * This function helps the browser to know which directory has been selected.
1227  * It should be called anywhere a confirm hit is done to open a new
1228  * directory/file which is a maildir/mbox.
1229  *
1230  * We could check if the sort method is appropriate with this feature.
1231  */
mutt_browser_select_dir(const char * f)1232 void mutt_browser_select_dir(const char *f)
1233 {
1234   init_lastdir();
1235 
1236   mutt_buffer_strcpy(&LastDirBackup, f);
1237 
1238   /* Method that will fetch the parent path depending on the type of the path. */
1239   char buf[PATH_MAX];
1240   mutt_get_parent_path(mutt_buffer_string(&LastDirBackup), buf, sizeof(buf));
1241   mutt_buffer_strcpy(&LastDir, buf);
1242 }
1243 
1244 /**
1245  * mutt_buffer_select_file - Let the user select a file
1246  * @param[in]  file     Buffer for the result
1247  * @param[in]  flags    Flags, see #SelectFileFlags
1248  * @param[in]  m        Mailbox
1249  * @param[out] files    Array of selected files
1250  * @param[out] numfiles Number of selected files
1251  */
mutt_buffer_select_file(struct Buffer * file,SelectFileFlags flags,struct Mailbox * m,char *** files,int * numfiles)1252 void mutt_buffer_select_file(struct Buffer *file, SelectFileFlags flags,
1253                              struct Mailbox *m, char ***files, int *numfiles)
1254 {
1255   struct BrowserState state = { { 0 } };
1256   struct Menu *menu = NULL;
1257   struct MuttWindow *dlg = NULL;
1258   bool kill_prefix = false;
1259   bool multiple = (flags & MUTT_SEL_MULTI);
1260   bool folder = (flags & MUTT_SEL_FOLDER);
1261   state.is_mailbox_list = (flags & MUTT_SEL_MAILBOX) && folder;
1262 
1263   /* Keeps in memory the directory we were in when hitting '='
1264    * to go directly to $folder (`$folder`) */
1265   char goto_swapper[PATH_MAX] = { 0 };
1266 
1267   struct Buffer *OldLastDir = mutt_buffer_pool_get();
1268   struct Buffer *tmp = mutt_buffer_pool_get();
1269   struct Buffer *buf = mutt_buffer_pool_get();
1270   struct Buffer *prefix = mutt_buffer_pool_get();
1271 
1272   init_lastdir();
1273 
1274 #ifdef USE_NNTP
1275   if (OptNews)
1276   {
1277     if (mutt_buffer_is_empty(file))
1278     {
1279       struct NntpAccountData *adata = CurrentNewsSrv;
1280 
1281       /* default state for news reader mode is browse subscribed newsgroups */
1282       state.is_mailbox_list = false;
1283       for (size_t i = 0; i < adata->groups_num; i++)
1284       {
1285         struct NntpMboxData *mdata = adata->groups_list[i];
1286         if (mdata && mdata->subscribed)
1287         {
1288           state.is_mailbox_list = true;
1289           break;
1290         }
1291       }
1292     }
1293     else
1294     {
1295       mutt_buffer_copy(prefix, file);
1296     }
1297   }
1298   else
1299 #endif
1300       if (!mutt_buffer_is_empty(file))
1301   {
1302     mutt_buffer_expand_path(file);
1303 #ifdef USE_IMAP
1304     if (imap_path_probe(mutt_buffer_string(file), NULL) == MUTT_IMAP)
1305     {
1306       init_state(&state, NULL);
1307       state.imap_browse = true;
1308       if (imap_browse(mutt_buffer_string(file), &state) == 0)
1309       {
1310         mutt_buffer_strcpy(&LastDir, state.folder);
1311         browser_sort(&state);
1312       }
1313     }
1314     else
1315     {
1316 #endif
1317       int i;
1318       for (i = mutt_buffer_len(file) - 1;
1319            (i > 0) && ((mutt_buffer_string(file))[i] != '/'); i--)
1320       {
1321         ; // do nothing
1322       }
1323 
1324       if (i > 0)
1325       {
1326         if ((mutt_buffer_string(file))[0] == '/')
1327           mutt_buffer_strcpy_n(&LastDir, mutt_buffer_string(file), i);
1328         else
1329         {
1330           mutt_path_getcwd(&LastDir);
1331           mutt_buffer_addch(&LastDir, '/');
1332           mutt_buffer_addstr_n(&LastDir, mutt_buffer_string(file), i);
1333         }
1334       }
1335       else
1336       {
1337         if ((mutt_buffer_string(file))[0] == '/')
1338           mutt_buffer_strcpy(&LastDir, "/");
1339         else
1340           mutt_path_getcwd(&LastDir);
1341       }
1342 
1343       if ((i <= 0) && (mutt_buffer_string(file)[0] != '/'))
1344         mutt_buffer_copy(prefix, file);
1345       else
1346         mutt_buffer_strcpy(prefix, mutt_buffer_string(file) + i + 1);
1347       kill_prefix = true;
1348 #ifdef USE_IMAP
1349     }
1350 #endif
1351   }
1352   else
1353   {
1354     if (!folder)
1355       mutt_path_getcwd(&LastDir);
1356     else
1357     {
1358       /* Whether we use the tracking feature of the browser depends
1359        * on which sort method we chose to use. This variable is defined
1360        * only to help readability of the code.  */
1361       bool browser_track = false;
1362 
1363       const short c_sort_browser = cs_subset_sort(NeoMutt->sub, "sort_browser");
1364       switch (c_sort_browser & SORT_MASK)
1365       {
1366         case SORT_DESC:
1367         case SORT_SUBJECT:
1368         case SORT_ORDER:
1369           browser_track = true;
1370           break;
1371       }
1372 
1373       /* We use mutt_browser_select_dir to initialize the two
1374        * variables (LastDir, LastDirBackup) at the appropriate
1375        * values.
1376        *
1377        * We do it only when LastDir is not set (first pass there)
1378        * or when CurrentFolder and LastDirBackup are not the same.
1379        * This code is executed only when we list files, not when
1380        * we press up/down keys to navigate in a displayed list.
1381        *
1382        * We only do this when CurrentFolder has been set (ie, not
1383        * when listing folders on startup with "neomutt -y").
1384        *
1385        * This tracker is only used when browser_track is true,
1386        * meaning only with sort methods SUBJECT/DESC for now.  */
1387       if (CurrentFolder)
1388       {
1389         if (mutt_buffer_is_empty(&LastDir))
1390         {
1391           /* If browsing in "local"-mode, than we chose to define LastDir to
1392            * MailDir */
1393           switch (mx_path_probe(CurrentFolder))
1394           {
1395             case MUTT_IMAP:
1396             case MUTT_MAILDIR:
1397             case MUTT_MBOX:
1398             case MUTT_MH:
1399             case MUTT_MMDF:
1400             {
1401               const char *const c_folder =
1402                   cs_subset_string(NeoMutt->sub, "folder");
1403               const char *const c_spool_file = cs_subset_string(NeoMutt->sub, "spool_file");
1404               if (c_folder)
1405                 mutt_buffer_strcpy(&LastDir, c_folder);
1406               else if (c_spool_file)
1407                 mutt_browser_select_dir(c_spool_file);
1408               break;
1409             }
1410             default:
1411               mutt_browser_select_dir(CurrentFolder);
1412               break;
1413           }
1414         }
1415         else if (!mutt_str_equal(CurrentFolder, mutt_buffer_string(&LastDirBackup)))
1416         {
1417           mutt_browser_select_dir(CurrentFolder);
1418         }
1419       }
1420 
1421       /* When browser tracking feature is disabled, clear LastDirBackup */
1422       if (!browser_track)
1423         mutt_buffer_reset(&LastDirBackup);
1424     }
1425 
1426 #ifdef USE_IMAP
1427     if (!state.is_mailbox_list &&
1428         (imap_path_probe(mutt_buffer_string(&LastDir), NULL) == MUTT_IMAP))
1429     {
1430       init_state(&state, NULL);
1431       state.imap_browse = true;
1432       imap_browse(mutt_buffer_string(&LastDir), &state);
1433       browser_sort(&state);
1434     }
1435     else
1436 #endif
1437     {
1438       size_t i = mutt_buffer_len(&LastDir);
1439       while ((i > 0) && (mutt_buffer_string(&LastDir)[--i] == '/'))
1440         LastDir.data[i] = '\0';
1441       mutt_buffer_fix_dptr(&LastDir);
1442       if (mutt_buffer_is_empty(&LastDir))
1443         mutt_path_getcwd(&LastDir);
1444     }
1445   }
1446 
1447   mutt_buffer_reset(file);
1448 
1449   const struct Mapping *help_data = NULL;
1450 #ifdef USE_NNTP
1451   if (OptNews)
1452     help_data = FolderNewsHelp;
1453   else
1454 #endif
1455     help_data = FolderHelp;
1456 
1457   dlg = simple_dialog_new(MENU_FOLDER, WT_DLG_BROWSER, help_data);
1458 
1459   menu = dlg->wdata;
1460   menu->make_entry = folder_make_entry;
1461   menu->search = select_file_search;
1462   if (multiple)
1463     menu->tag = file_tag;
1464 
1465   struct MuttWindow *sbar = window_find_child(dlg, WT_STATUS_BAR);
1466 
1467   struct MuttWindow *win_menu = menu->win;
1468 
1469   // NT_COLOR is handled by the SimpleDialog
1470   notify_observer_add(NeoMutt->notify, NT_CONFIG, browser_config_observer, menu);
1471   notify_observer_add(win_menu->notify, NT_WINDOW, browser_window_observer, win_menu);
1472 
1473   if (state.is_mailbox_list)
1474   {
1475     examine_mailboxes(m, NULL, &state);
1476   }
1477   else
1478 #ifdef USE_IMAP
1479       if (!state.imap_browse)
1480 #endif
1481   {
1482     // examine_directory() calls add_folder() which needs the menu
1483     if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1484                           mutt_buffer_string(prefix)) == -1)
1485     {
1486       goto bail;
1487     }
1488   }
1489 
1490   init_menu(&state, menu, m, sbar);
1491   // only now do we have a valid state to attach
1492   menu->mdata = &state.entry;
1493 
1494   int last_selected_mailbox = -1;
1495 
1496   while (true)
1497   {
1498     if (state.is_mailbox_list && (last_selected_mailbox >= 0) &&
1499         (last_selected_mailbox < menu->max))
1500     {
1501       menu_set_index(menu, last_selected_mailbox);
1502     }
1503     int op = menu_loop(menu);
1504     if (op >= 0)
1505       mutt_debug(LL_DEBUG1, "Got op %s (%d)\n", OpStrings[op][0], op);
1506     int index = menu_get_index(menu);
1507     struct FolderFile *ff = ARRAY_GET(&state.entry, index);
1508     switch (op)
1509     {
1510       case OP_DESCEND_DIRECTORY:
1511       case OP_GENERIC_SELECT_ENTRY:
1512       {
1513         if (ARRAY_EMPTY(&state.entry))
1514         {
1515           mutt_error(_("No files match the file mask"));
1516           break;
1517         }
1518 
1519         if (S_ISDIR(ff->mode) ||
1520             (S_ISLNK(ff->mode) && link_is_dir(mutt_buffer_string(&LastDir), ff->name))
1521 #ifdef USE_IMAP
1522             || ff->inferiors
1523 #endif
1524         )
1525         {
1526           /* make sure this isn't a MH or maildir mailbox */
1527           if (state.is_mailbox_list)
1528           {
1529             mutt_buffer_strcpy(buf, ff->name);
1530             mutt_buffer_expand_path(buf);
1531           }
1532 #ifdef USE_IMAP
1533           else if (state.imap_browse)
1534           {
1535             mutt_buffer_strcpy(buf, ff->name);
1536           }
1537 #endif
1538           else
1539           {
1540             mutt_buffer_concat_path(buf, mutt_buffer_string(&LastDir), ff->name);
1541           }
1542 
1543           enum MailboxType type = mx_path_probe(mutt_buffer_string(buf));
1544           if ((op == OP_DESCEND_DIRECTORY) || (type == MUTT_MAILBOX_ERROR) ||
1545               (type == MUTT_UNKNOWN)
1546 #ifdef USE_IMAP
1547               || ff->inferiors
1548 #endif
1549           )
1550           {
1551             /* save the old directory */
1552             mutt_buffer_copy(OldLastDir, &LastDir);
1553 
1554             if (mutt_str_equal(ff->name, ".."))
1555             {
1556               size_t lastdirlen = mutt_buffer_len(&LastDir);
1557               if ((lastdirlen > 1) &&
1558                   mutt_str_equal("..", mutt_buffer_string(&LastDir) + lastdirlen - 2))
1559               {
1560                 mutt_buffer_addstr(&LastDir, "/..");
1561               }
1562               else
1563               {
1564                 char *p = NULL;
1565                 if (lastdirlen > 1)
1566                   p = strrchr(mutt_buffer_string(&LastDir) + 1, '/');
1567 
1568                 if (p)
1569                 {
1570                   *p = '\0';
1571                   mutt_buffer_fix_dptr(&LastDir);
1572                 }
1573                 else
1574                 {
1575                   if (mutt_buffer_string(&LastDir)[0] == '/')
1576                     mutt_buffer_strcpy(&LastDir, "/");
1577                   else
1578                     mutt_buffer_addstr(&LastDir, "/..");
1579                 }
1580               }
1581             }
1582             else if (state.is_mailbox_list)
1583             {
1584               mutt_buffer_strcpy(&LastDir, ff->name);
1585               mutt_buffer_expand_path(&LastDir);
1586             }
1587 #ifdef USE_IMAP
1588             else if (state.imap_browse)
1589             {
1590               mutt_buffer_strcpy(&LastDir, ff->name);
1591               /* tack on delimiter here */
1592 
1593               /* special case "" needs no delimiter */
1594               struct Url *url = url_parse(ff->name);
1595               if (url && url->path && (ff->delim != '\0'))
1596               {
1597                 mutt_buffer_addch(&LastDir, ff->delim);
1598               }
1599               url_free(&url);
1600             }
1601 #endif
1602             else
1603             {
1604               mutt_buffer_concat_path(tmp, mutt_buffer_string(&LastDir), ff->name);
1605               mutt_buffer_copy(&LastDir, tmp);
1606             }
1607 
1608             destroy_state(&state);
1609             if (kill_prefix)
1610             {
1611               mutt_buffer_reset(prefix);
1612               kill_prefix = false;
1613             }
1614             state.is_mailbox_list = false;
1615 #ifdef USE_IMAP
1616             if (state.imap_browse)
1617             {
1618               init_state(&state, NULL);
1619               state.imap_browse = true;
1620               imap_browse(mutt_buffer_string(&LastDir), &state);
1621               browser_sort(&state);
1622               menu->mdata = &state.entry;
1623             }
1624             else
1625 #endif
1626             {
1627               if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1628                                     mutt_buffer_string(prefix)) == -1)
1629               {
1630                 /* try to restore the old values */
1631                 mutt_buffer_copy(&LastDir, OldLastDir);
1632                 if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1633                                       mutt_buffer_string(prefix)) == -1)
1634                 {
1635                   mutt_buffer_strcpy(&LastDir, NONULL(HomeDir));
1636                   goto bail;
1637                 }
1638               }
1639               /* resolve paths navigated from GUI */
1640               if (mutt_path_realpath(LastDir.data) == 0)
1641                 break;
1642             }
1643 
1644             browser_highlight_default(&state, menu);
1645             init_menu(&state, menu, m, sbar);
1646             goto_swapper[0] = '\0';
1647             break;
1648           }
1649         }
1650         else if (op == OP_DESCEND_DIRECTORY)
1651         {
1652           mutt_error(_("%s is not a directory"), ARRAY_GET(&state.entry, index)->name);
1653           break;
1654         }
1655 
1656         if (state.is_mailbox_list || OptNews) /* USE_NNTP */
1657         {
1658           mutt_buffer_strcpy(file, ff->name);
1659           mutt_buffer_expand_path(file);
1660         }
1661 #ifdef USE_IMAP
1662         else if (state.imap_browse)
1663           mutt_buffer_strcpy(file, ff->name);
1664 #endif
1665         else
1666         {
1667           mutt_buffer_concat_path(file, mutt_buffer_string(&LastDir), ff->name);
1668         }
1669       }
1670         /* fallthrough */
1671 
1672       case OP_EXIT:
1673 
1674         if (multiple)
1675         {
1676           char **tfiles = NULL;
1677 
1678           if (menu->tagged)
1679           {
1680             *numfiles = menu->tagged;
1681             tfiles = mutt_mem_calloc(*numfiles, sizeof(char *));
1682             size_t j = 0;
1683             ARRAY_FOREACH(ff, &state.entry)
1684             {
1685               if (ff->tagged)
1686               {
1687                 mutt_buffer_concat_path(tmp, mutt_buffer_string(&LastDir), ff->name);
1688                 mutt_buffer_expand_path(tmp);
1689                 tfiles[j++] = mutt_buffer_strdup(tmp);
1690               }
1691             }
1692             *files = tfiles;
1693           }
1694           else if (!mutt_buffer_is_empty(file)) /* no tagged entries. return selected entry */
1695           {
1696             *numfiles = 1;
1697             tfiles = mutt_mem_calloc(*numfiles, sizeof(char *));
1698             mutt_buffer_expand_path(file);
1699             tfiles[0] = mutt_buffer_strdup(file);
1700             *files = tfiles;
1701           }
1702         }
1703 
1704         destroy_state(&state);
1705         goto bail;
1706 
1707       case OP_BROWSER_TELL:
1708         if (!ARRAY_EMPTY(&state.entry))
1709           mutt_message("%s", ARRAY_GET(&state.entry, index)->name);
1710         break;
1711 
1712 #ifdef USE_IMAP
1713       case OP_BROWSER_TOGGLE_LSUB:
1714         bool_str_toggle(NeoMutt->sub, "imap_list_subscribed", NULL);
1715 
1716         mutt_unget_event(0, OP_CHECK_NEW);
1717         break;
1718 
1719       case OP_CREATE_MAILBOX:
1720         if (!state.imap_browse)
1721         {
1722           mutt_error(_("Create is only supported for IMAP mailboxes"));
1723           break;
1724         }
1725 
1726         if (imap_mailbox_create(mutt_buffer_string(&LastDir)) == 0)
1727         {
1728           /* TODO: find a way to detect if the new folder would appear in
1729            *   this window, and insert it without starting over. */
1730           destroy_state(&state);
1731           init_state(&state, NULL);
1732           state.imap_browse = true;
1733           imap_browse(mutt_buffer_string(&LastDir), &state);
1734           browser_sort(&state);
1735           menu->mdata = &state.entry;
1736           browser_highlight_default(&state, menu);
1737           init_menu(&state, menu, m, sbar);
1738         }
1739         /* else leave error on screen */
1740         break;
1741 
1742       case OP_RENAME_MAILBOX:
1743         if (!ff->imap)
1744           mutt_error(_("Rename is only supported for IMAP mailboxes"));
1745         else
1746         {
1747           if (imap_mailbox_rename(ff->name) >= 0)
1748           {
1749             destroy_state(&state);
1750             init_state(&state, NULL);
1751             state.imap_browse = true;
1752             imap_browse(mutt_buffer_string(&LastDir), &state);
1753             browser_sort(&state);
1754             menu->mdata = &state.entry;
1755             browser_highlight_default(&state, menu);
1756             init_menu(&state, menu, m, sbar);
1757           }
1758         }
1759         break;
1760 
1761       case OP_DELETE_MAILBOX:
1762         if (!ff->imap)
1763           mutt_error(_("Delete is only supported for IMAP mailboxes"));
1764         else
1765         {
1766           char msg[128];
1767 
1768           // TODO(sileht): It could be better to select INBOX instead. But I
1769           // don't want to manipulate Context/Mailboxes/mailbox->account here for now.
1770           // Let's just protect neomutt against crash for now. #1417
1771           if (mutt_str_equal(mailbox_path(m), ff->name))
1772           {
1773             mutt_error(_("Can't delete currently selected mailbox"));
1774             break;
1775           }
1776 
1777           snprintf(msg, sizeof(msg), _("Really delete mailbox \"%s\"?"), ff->name);
1778           if (mutt_yesorno(msg, MUTT_NO) == MUTT_YES)
1779           {
1780             if (imap_delete_mailbox(m, ff->name) == 0)
1781             {
1782               /* free the mailbox from the browser */
1783               FREE(&ff->name);
1784               FREE(&ff->desc);
1785               /* and move all other entries up */
1786               ARRAY_REMOVE(&state.entry, ff);
1787               mutt_message(_("Mailbox deleted"));
1788               init_menu(&state, menu, m, sbar);
1789             }
1790             else
1791               mutt_error(_("Mailbox deletion failed"));
1792           }
1793           else
1794             mutt_message(_("Mailbox not deleted"));
1795         }
1796         break;
1797 #endif
1798 
1799       case OP_GOTO_PARENT:
1800       case OP_CHANGE_DIRECTORY:
1801 
1802 #ifdef USE_NNTP
1803         if (OptNews)
1804           break;
1805 #endif
1806 
1807         mutt_buffer_copy(buf, &LastDir);
1808 #ifdef USE_IMAP
1809         if (!state.imap_browse)
1810 #endif
1811         {
1812           /* add '/' at the end of the directory name if not already there */
1813           size_t len = mutt_buffer_len(buf);
1814           if ((len > 0) && (mutt_buffer_string(&LastDir)[len - 1] != '/'))
1815             mutt_buffer_addch(buf, '/');
1816         }
1817 
1818         if (op == OP_CHANGE_DIRECTORY)
1819         {
1820           /* buf comes from the buffer pool, so defaults to size 1024 */
1821           int ret = mutt_buffer_get_field(_("Chdir to: "), buf, MUTT_FILE,
1822                                           false, NULL, NULL, NULL);
1823           if ((ret != 0) && mutt_buffer_is_empty(buf))
1824             break;
1825         }
1826         else if (op == OP_GOTO_PARENT)
1827           mutt_get_parent_path(mutt_buffer_string(buf), buf->data, buf->dsize);
1828 
1829         if (!mutt_buffer_is_empty(buf))
1830         {
1831           state.is_mailbox_list = false;
1832           mutt_buffer_expand_path(buf);
1833 #ifdef USE_IMAP
1834           if (imap_path_probe(mutt_buffer_string(buf), NULL) == MUTT_IMAP)
1835           {
1836             mutt_buffer_copy(&LastDir, buf);
1837             destroy_state(&state);
1838             init_state(&state, NULL);
1839             state.imap_browse = true;
1840             imap_browse(mutt_buffer_string(&LastDir), &state);
1841             browser_sort(&state);
1842             menu->mdata = &state.entry;
1843             browser_highlight_default(&state, menu);
1844             init_menu(&state, menu, m, sbar);
1845           }
1846           else
1847 #endif
1848           {
1849             if (mutt_buffer_string(buf)[0] != '/')
1850             {
1851               /* in case dir is relative, make it relative to LastDir,
1852                * not current working dir */
1853               mutt_buffer_concat_path(tmp, mutt_buffer_string(&LastDir),
1854                                       mutt_buffer_string(buf));
1855               mutt_buffer_copy(buf, tmp);
1856             }
1857             /* Resolve path from <chdir>
1858              * Avoids buildup such as /a/b/../../c
1859              * Symlinks are always unraveled to keep code simple */
1860             if (mutt_path_realpath(buf->data) == 0)
1861               break;
1862 
1863             struct stat st = { 0 };
1864             if (stat(mutt_buffer_string(buf), &st) == 0)
1865             {
1866               if (S_ISDIR(st.st_mode))
1867               {
1868                 destroy_state(&state);
1869                 if (examine_directory(m, menu, &state, mutt_buffer_string(buf),
1870                                       mutt_buffer_string(prefix)) == 0)
1871                 {
1872                   mutt_buffer_copy(&LastDir, buf);
1873                 }
1874                 else
1875                 {
1876                   mutt_error(_("Error scanning directory"));
1877                   if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
1878                                         mutt_buffer_string(prefix)) == -1)
1879                   {
1880                     goto bail;
1881                   }
1882                 }
1883                 browser_highlight_default(&state, menu);
1884                 init_menu(&state, menu, m, sbar);
1885               }
1886               else
1887                 mutt_error(_("%s is not a directory"), mutt_buffer_string(buf));
1888             }
1889             else
1890               mutt_perror(mutt_buffer_string(buf));
1891           }
1892         }
1893         break;
1894 
1895       case OP_ENTER_MASK:
1896       {
1897         const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
1898         mutt_buffer_strcpy(buf, c_mask ? c_mask->pattern : NULL);
1899         if (mutt_get_field(_("File Mask: "), buf->data, buf->dsize,
1900                            MUTT_COMP_NO_FLAGS, false, NULL, NULL) != 0)
1901         {
1902           break;
1903         }
1904 
1905         mutt_buffer_fix_dptr(buf);
1906 
1907         state.is_mailbox_list = false;
1908         /* assume that the user wants to see everything */
1909         if (mutt_buffer_is_empty(buf))
1910           mutt_buffer_strcpy(buf, ".");
1911 
1912         struct Buffer errmsg = mutt_buffer_make(256);
1913         int rc = cs_subset_str_string_set(NeoMutt->sub, "mask",
1914                                           mutt_buffer_string(buf), &errmsg);
1915         if (CSR_RESULT(rc) != CSR_SUCCESS)
1916         {
1917           if (!mutt_buffer_is_empty(&errmsg))
1918           {
1919             mutt_error("%s", mutt_buffer_string(&errmsg));
1920             mutt_buffer_dealloc(&errmsg);
1921           }
1922           break;
1923         }
1924         mutt_buffer_dealloc(&errmsg);
1925 
1926         destroy_state(&state);
1927 #ifdef USE_IMAP
1928         if (state.imap_browse)
1929         {
1930           init_state(&state, NULL);
1931           state.imap_browse = true;
1932           imap_browse(mutt_buffer_string(&LastDir), &state);
1933           browser_sort(&state);
1934           menu->mdata = &state.entry;
1935           init_menu(&state, menu, m, sbar);
1936         }
1937         else
1938 #endif
1939             if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir), NULL) == 0)
1940         {
1941           init_menu(&state, menu, m, sbar);
1942         }
1943         else
1944         {
1945           mutt_error(_("Error scanning directory"));
1946           goto bail;
1947         }
1948         kill_prefix = false;
1949         if (ARRAY_EMPTY(&state.entry))
1950         {
1951           mutt_error(_("No files match the file mask"));
1952           break;
1953         }
1954         break;
1955       }
1956 
1957       case OP_SORT:
1958       case OP_SORT_REVERSE:
1959 
1960       {
1961         bool resort = true;
1962         int sort = -1;
1963         int reverse = (op == OP_SORT_REVERSE);
1964 
1965         switch (mutt_multi_choice(
1966             (reverse) ?
1967                 /* L10N: The highlighted letters must match the "Sort" options */
1968                 _("Reverse sort by (d)ate, (a)lpha, si(z)e, d(e)scription, "
1969                   "(c)ount, ne(w) count, or do(n)'t sort?") :
1970                 /* L10N: The highlighted letters must match the "Reverse Sort" options */
1971                 _("Sort by (d)ate, (a)lpha, si(z)e, d(e)scription, (c)ount, "
1972                   "ne(w) count, or do(n)'t sort?"),
1973             /* L10N: These must match the highlighted letters from "Sort" and "Reverse Sort" */
1974             _("dazecwn")))
1975         {
1976           case -1: /* abort */
1977             resort = false;
1978             break;
1979 
1980           case 1: /* (d)ate */
1981             sort = SORT_DATE;
1982             break;
1983 
1984           case 2: /* (a)lpha */
1985             sort = SORT_SUBJECT;
1986             break;
1987 
1988           case 3: /* si(z)e */
1989             sort = SORT_SIZE;
1990             break;
1991 
1992           case 4: /* d(e)scription */
1993             sort = SORT_DESC;
1994             break;
1995 
1996           case 5: /* (c)ount */
1997             sort = SORT_COUNT;
1998             break;
1999 
2000           case 6: /* ne(w) count */
2001             sort = SORT_UNREAD;
2002             break;
2003 
2004           case 7: /* do(n)'t sort */
2005             sort = SORT_ORDER;
2006             break;
2007         }
2008         if (resort)
2009         {
2010           sort |= reverse ? SORT_REVERSE : 0;
2011           cs_subset_str_native_set(NeoMutt->sub, "sort_browser", sort, NULL);
2012           browser_sort(&state);
2013           browser_highlight_default(&state, menu);
2014           menu_queue_redraw(menu, MENU_REDRAW_FULL);
2015         }
2016         else
2017         {
2018           cs_subset_str_native_set(NeoMutt->sub, "sort_browser", sort, NULL);
2019         }
2020         break;
2021       }
2022 
2023       case OP_TOGGLE_MAILBOXES:
2024       case OP_BROWSER_GOTO_FOLDER:
2025       case OP_CHECK_NEW:
2026         if (state.is_mailbox_list)
2027         {
2028           last_selected_mailbox = menu->current;
2029         }
2030 
2031         if (op == OP_TOGGLE_MAILBOXES)
2032         {
2033           state.is_mailbox_list = !state.is_mailbox_list;
2034         }
2035 
2036         if (op == OP_BROWSER_GOTO_FOLDER)
2037         {
2038           /* When in mailboxes mode, disables this feature */
2039           const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
2040           if (c_folder)
2041           {
2042             mutt_debug(LL_DEBUG3, "= hit! Folder: %s, LastDir: %s\n", c_folder,
2043                        mutt_buffer_string(&LastDir));
2044             if (goto_swapper[0] == '\0')
2045             {
2046               if (!mutt_str_equal(mutt_buffer_string(&LastDir), c_folder))
2047               {
2048                 /* Stores into goto_swapper LastDir, and swaps to `$folder` */
2049                 mutt_str_copy(goto_swapper, mutt_buffer_string(&LastDir),
2050                               sizeof(goto_swapper));
2051                 mutt_buffer_copy(&LastDirBackup, &LastDir);
2052                 mutt_buffer_strcpy(&LastDir, c_folder);
2053               }
2054             }
2055             else
2056             {
2057               mutt_buffer_copy(&LastDirBackup, &LastDir);
2058               mutt_buffer_strcpy(&LastDir, goto_swapper);
2059               goto_swapper[0] = '\0';
2060             }
2061           }
2062         }
2063         destroy_state(&state);
2064         mutt_buffer_reset(prefix);
2065         kill_prefix = false;
2066 
2067         if (state.is_mailbox_list)
2068         {
2069           examine_mailboxes(m, menu, &state);
2070         }
2071 #ifdef USE_IMAP
2072         else if (imap_path_probe(mutt_buffer_string(&LastDir), NULL) == MUTT_IMAP)
2073         {
2074           init_state(&state, NULL);
2075           state.imap_browse = true;
2076           imap_browse(mutt_buffer_string(&LastDir), &state);
2077           browser_sort(&state);
2078           menu->mdata = &state.entry;
2079         }
2080 #endif
2081         else if (examine_directory(m, menu, &state, mutt_buffer_string(&LastDir),
2082                                    mutt_buffer_string(prefix)) == -1)
2083         {
2084           goto bail;
2085         }
2086         init_menu(&state, menu, m, sbar);
2087         break;
2088 
2089       case OP_MAILBOX_LIST:
2090         mutt_mailbox_list();
2091         break;
2092 
2093       case OP_BROWSER_NEW_FILE:
2094         mutt_buffer_printf(buf, "%s/", mutt_buffer_string(&LastDir));
2095         /* buf comes from the buffer pool, so defaults to size 1024 */
2096         if (mutt_buffer_get_field(_("New file name: "), buf, MUTT_FILE, false,
2097                                   NULL, NULL, NULL) == 0)
2098         {
2099           mutt_buffer_copy(file, buf);
2100           destroy_state(&state);
2101           goto bail;
2102         }
2103         break;
2104 
2105       case OP_BROWSER_VIEW_FILE:
2106         if (ARRAY_EMPTY(&state.entry))
2107         {
2108           mutt_error(_("No files match the file mask"));
2109           break;
2110         }
2111 
2112 #ifdef USE_IMAP
2113         if (ff->selectable)
2114         {
2115           mutt_buffer_strcpy(file, ff->name);
2116           destroy_state(&state);
2117           goto bail;
2118         }
2119         else
2120 #endif
2121             if (S_ISDIR(ff->mode) ||
2122                 (S_ISLNK(ff->mode) && link_is_dir(mutt_buffer_string(&LastDir), ff->name)))
2123         {
2124           mutt_error(_("Can't view a directory"));
2125           break;
2126         }
2127         else
2128         {
2129           char buf2[PATH_MAX];
2130 
2131           mutt_path_concat(buf2, mutt_buffer_string(&LastDir), ff->name, sizeof(buf2));
2132           struct Body *b = mutt_make_file_attach(buf2, NeoMutt->sub);
2133           if (b)
2134           {
2135             mutt_view_attachment(NULL, b, MUTT_VA_REGULAR, NULL, NULL, menu->win);
2136             mutt_body_free(&b);
2137             menu_queue_redraw(menu, MENU_REDRAW_FULL);
2138           }
2139           else
2140             mutt_error(_("Error trying to view file"));
2141         }
2142         break;
2143 
2144 #ifdef USE_NNTP
2145       case OP_CATCHUP:
2146       case OP_UNCATCHUP:
2147       {
2148         if (!OptNews)
2149           break;
2150 
2151         struct NntpMboxData *mdata = NULL;
2152 
2153         int rc = nntp_newsrc_parse(CurrentNewsSrv);
2154         if (rc < 0)
2155           break;
2156 
2157         if (op == OP_CATCHUP)
2158           mdata = mutt_newsgroup_catchup(m, CurrentNewsSrv, ff->name);
2159         else
2160           mdata = mutt_newsgroup_uncatchup(m, CurrentNewsSrv, ff->name);
2161 
2162         if (mdata)
2163         {
2164           nntp_newsrc_update(CurrentNewsSrv);
2165           index = menu_get_index(menu) + 1;
2166           if (index < menu->max)
2167             menu_set_index(menu, index);
2168         }
2169         if (rc)
2170           menu_queue_redraw(menu, MENU_REDRAW_INDEX);
2171         nntp_newsrc_close(CurrentNewsSrv);
2172         break;
2173       }
2174 
2175       case OP_LOAD_ACTIVE:
2176       {
2177         if (!OptNews)
2178           break;
2179 
2180         struct NntpAccountData *adata = CurrentNewsSrv;
2181 
2182         if (nntp_newsrc_parse(adata) < 0)
2183           break;
2184 
2185         for (size_t i = 0; i < adata->groups_num; i++)
2186         {
2187           struct NntpMboxData *mdata = adata->groups_list[i];
2188           if (mdata)
2189             mdata->deleted = true;
2190         }
2191         nntp_active_fetch(adata, true);
2192         nntp_newsrc_update(adata);
2193         nntp_newsrc_close(adata);
2194 
2195         destroy_state(&state);
2196         if (state.is_mailbox_list)
2197         {
2198           examine_mailboxes(m, menu, &state);
2199         }
2200         else
2201         {
2202           if (examine_directory(m, menu, &state, NULL, NULL) == -1)
2203             break;
2204         }
2205         init_menu(&state, menu, m, sbar);
2206         break;
2207       }
2208 #endif /* USE_NNTP */
2209 
2210 #if defined(USE_IMAP) || defined(USE_NNTP)
2211       case OP_BROWSER_SUBSCRIBE:
2212       case OP_BROWSER_UNSUBSCRIBE:
2213 #endif
2214 #ifdef USE_NNTP
2215       case OP_SUBSCRIBE_PATTERN:
2216       case OP_UNSUBSCRIBE_PATTERN:
2217       {
2218         if (OptNews)
2219         {
2220           struct NntpAccountData *adata = CurrentNewsSrv;
2221           regex_t rx;
2222           memset(&rx, 0, sizeof(rx));
2223           char *s = buf->data;
2224           index = menu_get_index(menu);
2225 
2226           if ((op == OP_SUBSCRIBE_PATTERN) || (op == OP_UNSUBSCRIBE_PATTERN))
2227           {
2228             char tmp2[256];
2229 
2230             mutt_buffer_reset(buf);
2231             if (op == OP_SUBSCRIBE_PATTERN)
2232               snprintf(tmp2, sizeof(tmp2), _("Subscribe pattern: "));
2233             else
2234               snprintf(tmp2, sizeof(tmp2), _("Unsubscribe pattern: "));
2235             /* buf comes from the buffer pool, so defaults to size 1024 */
2236             if ((mutt_buffer_get_field(tmp2, buf, MUTT_PATTERN, false, NULL, NULL, NULL) != 0) ||
2237                 mutt_buffer_is_empty(buf))
2238             {
2239               break;
2240             }
2241 
2242             int err = REG_COMP(&rx, s, REG_NOSUB);
2243             if (err != 0)
2244             {
2245               regerror(err, &rx, buf->data, buf->dsize);
2246               regfree(&rx);
2247               mutt_error("%s", mutt_buffer_string(buf));
2248               break;
2249             }
2250             menu_queue_redraw(menu, MENU_REDRAW_FULL);
2251             index = 0;
2252           }
2253           else if (ARRAY_EMPTY(&state.entry))
2254           {
2255             mutt_error(_("No newsgroups match the mask"));
2256             break;
2257           }
2258 
2259           int rc = nntp_newsrc_parse(adata);
2260           if (rc < 0)
2261             break;
2262 
2263           ARRAY_FOREACH_FROM(ff, &state.entry, index)
2264           {
2265             if ((op == OP_BROWSER_SUBSCRIBE) || (op == OP_BROWSER_UNSUBSCRIBE) ||
2266                 (regexec(&rx, ff->name, 0, NULL, 0) == 0))
2267             {
2268               if ((op == OP_BROWSER_SUBSCRIBE) || (op == OP_SUBSCRIBE_PATTERN))
2269                 mutt_newsgroup_subscribe(adata, ff->name);
2270               else
2271                 mutt_newsgroup_unsubscribe(adata, ff->name);
2272             }
2273             if ((op == OP_BROWSER_SUBSCRIBE) || (op == OP_BROWSER_UNSUBSCRIBE))
2274             {
2275               if ((index + 1) < menu->max)
2276                 menu_set_index(menu, index + 1);
2277               break;
2278             }
2279           }
2280 
2281           if (op == OP_SUBSCRIBE_PATTERN)
2282           {
2283             for (size_t j = 0; adata && (j < adata->groups_num); j++)
2284             {
2285               struct NntpMboxData *mdata = adata->groups_list[j];
2286               if (mdata && mdata->group && !mdata->subscribed)
2287               {
2288                 if (regexec(&rx, mdata->group, 0, NULL, 0) == 0)
2289                 {
2290                   mutt_newsgroup_subscribe(adata, mdata->group);
2291                   add_folder(menu, &state, mdata->group, NULL, NULL, NULL, mdata);
2292                 }
2293               }
2294             }
2295             init_menu(&state, menu, m, sbar);
2296           }
2297           if (rc > 0)
2298             menu_queue_redraw(menu, MENU_REDRAW_FULL);
2299           nntp_newsrc_update(adata);
2300           nntp_clear_cache(adata);
2301           nntp_newsrc_close(adata);
2302           if ((op != OP_BROWSER_SUBSCRIBE) && (op != OP_BROWSER_UNSUBSCRIBE))
2303             regfree(&rx);
2304         }
2305 #ifdef USE_IMAP
2306         else
2307 #endif /* USE_IMAP && USE_NNTP */
2308 #endif /* USE_NNTP */
2309 #ifdef USE_IMAP
2310         {
2311           char tmp2[256];
2312           mutt_str_copy(tmp2, ff->name, sizeof(tmp2));
2313           mutt_expand_path(tmp2, sizeof(tmp2));
2314           imap_subscribe(tmp2, (op == OP_BROWSER_SUBSCRIBE));
2315         }
2316 #endif /* USE_IMAP */
2317       }
2318     }
2319   }
2320 
2321 bail:
2322   mutt_buffer_pool_release(&OldLastDir);
2323   mutt_buffer_pool_release(&tmp);
2324   mutt_buffer_pool_release(&buf);
2325   mutt_buffer_pool_release(&prefix);
2326 
2327   simple_dialog_free(&dlg);
2328 
2329   goto_swapper[0] = '\0';
2330 }
2331 
2332 /**
2333  * mutt_select_file - Let the user select a file
2334  * @param[in]  file     Buffer for the result
2335  * @param[in]  filelen  Length of buffer
2336  * @param[in]  flags    Flags, see #SelectFileFlags
2337  * @param[in]  m        Mailbox
2338  * @param[out] files    Array of selected files
2339  * @param[out] numfiles Number of selected files
2340  */
mutt_select_file(char * file,size_t filelen,SelectFileFlags flags,struct Mailbox * m,char *** files,int * numfiles)2341 void mutt_select_file(char *file, size_t filelen, SelectFileFlags flags,
2342                       struct Mailbox *m, char ***files, int *numfiles)
2343 {
2344   struct Buffer *f_buf = mutt_buffer_pool_get();
2345 
2346   mutt_buffer_strcpy(f_buf, NONULL(file));
2347   mutt_buffer_select_file(f_buf, flags, m, files, numfiles);
2348   mutt_str_copy(file, mutt_buffer_string(f_buf), filelen);
2349 
2350   mutt_buffer_pool_release(&f_buf);
2351 }
2352