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