NeoMutt  2023-05-17-16-g61469c
Teaching an old dog new tricks
DOXYGEN
browse.c
Go to the documentation of this file.
1
30#include "config.h"
31#include <limits.h>
32#include <stdbool.h>
33#include <stdio.h>
34#include <string.h>
35#include "private.h"
36#include "mutt/lib.h"
37#include "config/lib.h"
38#include "email/lib.h"
39#include "core/lib.h"
40#include "conn/lib.h"
41#include "mutt.h"
42#include "lib.h"
43#include "browser/lib.h"
44#include "enter/lib.h"
45#include "adata.h"
46#include "mdata.h"
47#include "mutt_logging.h"
48#include "muttlib.h"
49
62static void add_folder(char delim, char *folder, bool noselect, bool noinferiors,
63 struct BrowserState *state, bool isparent)
64{
65 char tmp[PATH_MAX] = { 0 };
66 char relpath[PATH_MAX] = { 0 };
67 struct ConnAccount cac = { { 0 } };
68 char mailbox[1024] = { 0 };
69 struct FolderFile ff = { 0 };
70
71 if (imap_parse_path(state->folder, &cac, mailbox, sizeof(mailbox)))
72 return;
73
74 if (isparent)
75 {
76 /* render superiors as unix-standard ".." */
77 mutt_str_copy(relpath, "../", sizeof(relpath));
78 }
79 else if (mutt_str_startswith(folder, mailbox))
80 {
81 /* strip current folder from target, to render a relative path */
82 mutt_str_copy(relpath, folder + mutt_str_len(mailbox), sizeof(relpath));
83 }
84 else
85 {
86 mutt_str_copy(relpath, folder, sizeof(relpath));
87 }
88
89 /* apply filemask filter. This should really be done at menu setup rather
90 * than at scan, since it's so expensive to scan. But that's big changes
91 * to browser.c */
92 const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
93 if (!mutt_regex_match(c_mask, relpath))
94 {
95 return;
96 }
97
98 imap_qualify_path(tmp, sizeof(tmp), &cac, folder);
99 ff.name = mutt_str_dup(tmp);
100
101 /* mark desc with delim in browser if it can have subfolders */
102 if (!isparent && !noinferiors && (strlen(relpath) < sizeof(relpath) - 1))
103 {
104 relpath[strlen(relpath) + 1] = '\0';
105 relpath[strlen(relpath)] = delim;
106 }
107
108 ff.desc = mutt_str_dup(relpath);
109 ff.imap = true;
110
111 /* delimiter at the root is useless. */
112 if (folder[0] == '\0')
113 delim = '\0';
114 ff.delim = delim;
115 ff.selectable = !noselect;
116 ff.inferiors = !noinferiors;
117
118 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
120 struct MailboxNode *np = NULL;
121 STAILQ_FOREACH(np, &ml, entries)
122 {
123 if (mutt_str_equal(tmp, mailbox_path(np->mailbox)))
124 break;
125 }
126
127 if (np)
128 {
129 ff.has_mailbox = true;
130 ff.has_new_mail = np->mailbox->has_new;
131 ff.msg_count = np->mailbox->msg_count;
132 ff.msg_unread = np->mailbox->msg_unread;
133 }
135
136 ARRAY_ADD(&state->entry, ff);
137}
138
148static int browse_add_list_result(struct ImapAccountData *adata, const char *cmd,
149 struct BrowserState *bstate, bool isparent)
150{
151 struct ImapList list = { 0 };
152 int rc;
153 struct Url *url = url_parse(bstate->folder);
154 if (!url)
155 return -1;
156
157 imap_cmd_start(adata, cmd);
158 adata->cmdresult = &list;
159 do
160 {
161 list.name = NULL;
162 rc = imap_cmd_step(adata);
163
164 if ((rc == IMAP_RES_CONTINUE) && list.name)
165 {
166 /* Let a parent folder never be selectable for navigation */
167 if (isparent)
168 list.noselect = true;
169 /* prune current folder from output */
170 if (isparent || !mutt_str_startswith(url->path, list.name))
171 add_folder(list.delim, list.name, list.noselect, list.noinferiors, bstate, isparent);
172 }
173 } while (rc == IMAP_RES_CONTINUE);
174 adata->cmdresult = NULL;
175
176 url_free(&url);
177
178 return (rc == IMAP_RES_OK) ? 0 : -1;
179}
180
190int imap_browse(const char *path, struct BrowserState *state)
191{
192 struct ImapAccountData *adata = NULL;
193 struct ImapList list = { 0 };
194 struct ConnAccount cac = { { 0 } };
195 char buf[PATH_MAX + 16];
196 char mbox[PATH_MAX] = { 0 };
197 char munged_mbox[PATH_MAX] = { 0 };
198 const char *list_cmd = NULL;
199 int len;
200 int n;
201 char ctmp;
202 bool showparents = false;
203
204 if (imap_parse_path(path, &cac, buf, sizeof(buf)))
205 {
206 mutt_error(_("%s is an invalid IMAP path"), path);
207 return -1;
208 }
209
210 const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
211 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed", false, NULL);
212
213 // Pick first mailbox connected to the same server
214 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
216 struct MailboxNode *np = NULL;
217 STAILQ_FOREACH(np, &ml, entries)
218 {
219 adata = imap_adata_get(np->mailbox);
220 // Pick first mailbox connected on the same server
221 if (imap_account_match(&adata->conn->account, &cac))
222 break;
223 adata = NULL;
224 }
226 if (!adata)
227 goto fail;
228
229 const bool c_imap_list_subscribed = cs_subset_bool(NeoMutt->sub, "imap_list_subscribed");
230 if (c_imap_list_subscribed)
231 {
232 /* RFC3348 section 3 states LSUB is unreliable for hierarchy information.
233 * The newer LIST extensions are designed for this. */
235 list_cmd = "LIST (SUBSCRIBED RECURSIVEMATCH)";
236 else
237 list_cmd = "LSUB";
238 }
239 else
240 {
241 list_cmd = "LIST";
242 }
243
244 mutt_message(_("Getting folder list..."));
245
246 /* skip check for parents when at the root */
247 if (buf[0] == '\0')
248 {
249 mbox[0] = '\0';
250 n = 0;
251 }
252 else
253 {
254 imap_fix_path(adata->delim, buf, mbox, sizeof(mbox));
255 n = mutt_str_len(mbox);
256 }
257
258 if (n)
259 {
260 int rc;
261 mutt_debug(LL_DEBUG3, "mbox: %s\n", mbox);
262
263 /* if our target exists and has inferiors, enter it if we
264 * aren't already going to */
265 imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), mbox);
266 len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
268 snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
269 imap_cmd_start(adata, buf);
270 adata->cmdresult = &list;
271 do
272 {
273 list.name = 0;
274 rc = imap_cmd_step(adata);
275 if ((rc == IMAP_RES_CONTINUE) && list.name)
276 {
277 if (!list.noinferiors && list.name[0] &&
278 (imap_mxcmp(list.name, mbox) == 0) && (n < sizeof(mbox) - 1))
279 {
280 mbox[n++] = list.delim;
281 mbox[n] = '\0';
282 }
283 }
284 } while (rc == IMAP_RES_CONTINUE);
285 adata->cmdresult = NULL;
286
287 /* if we're descending a folder, mark it as current in browser_state */
288 if (mbox[n - 1] == list.delim)
289 {
290 showparents = true;
291 imap_qualify_path(buf, sizeof(buf), &cac, mbox);
292 state->folder = mutt_str_dup(buf);
293 n--;
294 }
295
296 /* Find superiors to list
297 * Note: UW-IMAP servers return folder + delimiter when asked to list
298 * folder + delimiter. Cyrus servers don't. So we ask for folder,
299 * and tack on delimiter ourselves.
300 * Further note: UW-IMAP servers return nothing when asked for
301 * NAMESPACES without delimiters at the end. Argh! */
302 for (n--; n >= 0 && mbox[n] != list.delim; n--)
303 ; // do nothing
304
305 if (n > 0) /* "aaaa/bbbb/" -> "aaaa" */
306 {
307 /* forget the check, it is too delicate (see above). Have we ever
308 * had the parent not exist? */
309 ctmp = mbox[n];
310 mbox[n] = '\0';
311
312 if (showparents)
313 {
314 mutt_debug(LL_DEBUG3, "adding parent %s\n", mbox);
315 add_folder(list.delim, mbox, true, false, state, true);
316 }
317
318 /* if our target isn't a folder, we are in our superior */
319 if (!state->folder)
320 {
321 /* store folder with delimiter */
322 mbox[n++] = ctmp;
323 ctmp = mbox[n];
324 mbox[n] = '\0';
325 imap_qualify_path(buf, sizeof(buf), &cac, mbox);
326 state->folder = mutt_str_dup(buf);
327 }
328 mbox[n] = ctmp;
329 }
330 else
331 {
332 /* "/bbbb/" -> add "/", "aaaa/" -> add "" */
333 char relpath[2] = { 0 };
334 /* folder may be "/" */
335 snprintf(relpath, sizeof(relpath), "%c", (n < 0) ? '\0' : adata->delim);
336 if (showparents)
337 add_folder(adata->delim, relpath, true, false, state, true);
338 if (!state->folder)
339 {
340 imap_qualify_path(buf, sizeof(buf), &cac, relpath);
341 state->folder = mutt_str_dup(buf);
342 }
343 }
344 }
345
346 /* no namespace, no folder: set folder to host only */
347 if (!state->folder)
348 {
349 imap_qualify_path(buf, sizeof(buf), &cac, NULL);
350 state->folder = mutt_str_dup(buf);
351 }
352
353 mutt_debug(LL_DEBUG3, "Quoting mailbox scan: %s -> ", mbox);
354 snprintf(buf, sizeof(buf), "%s%%", mbox);
355 imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), buf);
356 mutt_debug(LL_DEBUG3, "%s\n", munged_mbox);
357 len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
359 snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
360 if (browse_add_list_result(adata, buf, state, false))
361 goto fail;
362
363 if (ARRAY_EMPTY(&state->entry))
364 {
365 // L10N: (%s) is the name / path of the folder we were trying to browse
366 mutt_error(_("No such folder: %s"), path);
367 goto fail;
368 }
369
371
372 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
373 c_imap_check_subscribed, NULL);
374 return 0;
375
376fail:
377 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
378 c_imap_check_subscribed, NULL);
379 return -1;
380}
381
390int imap_mailbox_create(const char *path)
391{
392 struct ImapAccountData *adata = NULL;
393 struct ImapMboxData *mdata = NULL;
394 struct Buffer *name = buf_pool_get();
395 int rc = -1;
396
397 if (imap_adata_find(path, &adata, &mdata) < 0)
398 {
399 mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path);
400 goto done;
401 }
402
403 /* append a delimiter if necessary */
404 const size_t n = buf_strcpy(name, mdata->real_name);
405 if ((n != 0) && (name->data[n - 1] != adata->delim))
406 {
407 buf_addch(name, adata->delim);
408 }
409
410 if (buf_get_field(_("Create mailbox: "), name, MUTT_COMP_FILE, false, NULL, NULL, NULL) != 0)
411 {
412 goto done;
413 }
414
415 if (buf_is_empty(name))
416 {
417 mutt_error(_("Mailbox must have a name"));
418 goto done;
419 }
420
421 if (imap_create_mailbox(adata, buf_string(name)) < 0)
422 goto done;
423
424 imap_mdata_free((void *) &mdata);
425 mutt_message(_("Mailbox created"));
426 mutt_sleep(0);
427 rc = 0;
428
429done:
430 imap_mdata_free((void *) &mdata);
431 buf_pool_release(&name);
432 return rc;
433}
434
443int imap_mailbox_rename(const char *path)
444{
445 struct ImapAccountData *adata = NULL;
446 struct ImapMboxData *mdata = NULL;
447 struct Buffer *buf = NULL;
448 struct Buffer *newname = NULL;
449 int rc = -1;
450
451 if (imap_adata_find(path, &adata, &mdata) < 0)
452 {
453 mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path);
454 goto done;
455 }
456
457 if (mdata->real_name[0] == '\0')
458 {
459 mutt_error(_("Can't rename root folder"));
460 goto done;
461 }
462
463 buf = buf_pool_get();
464 newname = buf_pool_get();
465
466 buf_printf(buf, _("Rename mailbox %s to: "), mdata->name);
467 buf_strcpy(newname, mdata->name);
468
469 if (buf_get_field(buf_string(buf), newname, MUTT_COMP_FILE, false, NULL, NULL, NULL) != 0)
470 {
471 goto done;
472 }
473
474 if (buf_is_empty(newname))
475 {
476 mutt_error(_("Mailbox must have a name"));
477 goto done;
478 }
479
480 imap_fix_path(adata->delim, buf_string(newname), buf->data, buf->dsize);
481
482 if (imap_rename_mailbox(adata, mdata->name, buf_string(buf)) < 0)
483 {
484 mutt_error(_("Rename failed: %s"), imap_get_qualifier(adata->buf));
485 goto done;
486 }
487
488 mutt_message(_("Mailbox renamed"));
489 mutt_sleep(0);
490 rc = 0;
491
492done:
493 imap_mdata_free((void *) &mdata);
494 buf_pool_release(&buf);
495 buf_pool_release(&newname);
496
497 return rc;
498}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:155
#define ARRAY_EMPTY(head)
Check if an array is empty.
Definition: array.h:73
Select a Mailbox from a list.
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:171
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:301
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:251
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:370
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:78
const struct Regex * cs_subset_regex(const struct ConfigSubset *sub, const char *name)
Get a regex config item by name.
Definition: helpers.c:243
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Convenience wrapper for the config headers.
Connection Library.
Convenience wrapper for the core headers.
Structs that make up an email.
Enter a string.
int buf_get_field(const char *field, struct Buffer *buf, CompletionFlags complete, bool multiple, struct Mailbox *m, char ***files, int *numfiles)
Ask the user for a string.
Definition: window.c:180
#define mutt_error(...)
Definition: logging2.h:87
#define mutt_message(...)
Definition: logging2.h:86
#define mutt_debug(LEVEL,...)
Definition: logging2.h:84
struct ImapAccountData * imap_adata_get(struct Mailbox *m)
Get the Account data for this mailbox.
Definition: adata.c:90
int imap_browse(const char *path, struct BrowserState *state)
IMAP hook into the folder browser.
Definition: browse.c:190
static int browse_add_list_result(struct ImapAccountData *adata, const char *cmd, struct BrowserState *bstate, bool isparent)
Add entries to the folder browser.
Definition: browse.c:148
static void add_folder(char delim, char *folder, bool noselect, bool noinferiors, struct BrowserState *state, bool isparent)
Format and add an IMAP folder to the browser.
Definition: browse.c:62
int imap_mailbox_create(const char *path)
Create a new IMAP mailbox.
Definition: browse.c:390
int imap_mailbox_rename(const char *path)
Rename a mailbox.
Definition: browse.c:443
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
Given an IMAP command, send it to the server.
Definition: command.c:1095
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition: command.c:1109
int imap_parse_path(const char *path, struct ConnAccount *cac, char *mailbox, size_t mailboxlen)
Parse an IMAP mailbox name into ConnAccount, name.
Definition: util.c:473
int imap_mxcmp(const char *mx1, const char *mx2)
Compare mailbox names, giving priority to INBOX.
Definition: util.c:544
void imap_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: mdata.c:40
void imap_qualify_path(char *buf, size_t buflen, struct ConnAccount *conn_account, char *path)
Make an absolute IMAP folder target.
Definition: util.c:815
#define IMAP_RES_OK
<tag> OK ...
Definition: private.h:56
char * imap_fix_path(char delim, const char *mailbox, char *path, size_t plen)
Fix up the imap path.
Definition: util.c:678
#define IMAP_CAP_LIST_EXTENDED
RFC5258: IMAP4 LIST Command Extensions.
Definition: private.h:140
int imap_adata_find(const char *path, struct ImapAccountData **adata, struct ImapMboxData **mdata)
Find the Account data for this path.
Definition: util.c:71
bool imap_account_match(const struct ConnAccount *a1, const struct ConnAccount *a2)
Compare two Accounts.
Definition: util.c:1039
void imap_munge_mbox_name(bool unicode, char *dest, size_t dlen, const char *src)
Quote awkward characters in a mailbox name.
Definition: util.c:906
#define IMAP_RES_CONTINUE
* ...
Definition: private.h:57
char * imap_get_qualifier(char *buf)
Get the qualifier from a tagged response.
Definition: util.c:767
int imap_rename_mailbox(struct ImapAccountData *adata, char *oldname, const char *newname)
Rename a mailbox.
Definition: imap.c:496
int imap_create_mailbox(struct ImapAccountData *adata, const char *mailbox)
Create a new mailbox.
Definition: imap.c:455
@ LL_DEBUG3
Log at debug level 3.
Definition: logging2.h:42
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:40
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:209
@ MUTT_IMAP
'IMAP' Mailbox type
Definition: mailbox.h:50
@ MUTT_MAILBOX_ANY
Match any Mailbox type.
Definition: mailbox.h:42
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
bool mutt_regex_match(const struct Regex *regex, const char *str)
Shorthand to mutt_regex_capture()
Definition: regex.c:635
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:228
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:568
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:653
Many unsorted constants and some structs.
#define MUTT_COMP_FILE
File completion (in browser)
Definition: mutt.h:58
#define PATH_MAX
Definition: mutt.h:41
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:73
NeoMutt Logging.
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1424
Some miscellaneous functions.
void neomutt_mailboxlist_clear(struct MailboxList *ml)
Free a Mailbox List.
Definition: neomutt.c:141
size_t neomutt_mailboxlist_get_all(struct MailboxList *head, struct NeoMutt *n, enum MailboxType type)
Get a List of all Mailboxes.
Definition: neomutt.c:164
Notmuch-specific Mailbox data.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:106
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:119
Pop-specific Account data.
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
GUI display the mailboxes in a side panel.
Key value store.
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
State of the file/mailbox browser.
Definition: lib.h:111
char * folder
Folder name.
Definition: lib.h:115
struct BrowserStateEntry entry
Array of files / dirs / mailboxes.
Definition: lib.h:112
String manipulation buffer.
Definition: buffer.h:34
size_t dsize
Length of data.
Definition: buffer.h:37
char * data
Pointer to data.
Definition: buffer.h:35
Login details for a remote server.
Definition: connaccount.h:53
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:50
Browser entry representing a folder/dir.
Definition: lib.h:73
bool selectable
Folder can be selected.
Definition: lib.h:92
char delim
Path delimiter.
Definition: lib.h:89
bool imap
This is an IMAP folder.
Definition: lib.h:91
bool has_mailbox
This is a mailbox.
Definition: lib.h:95
char * name
Name of file/dir/mailbox.
Definition: lib.h:81
bool has_new_mail
true if mailbox has "new mail"
Definition: lib.h:84
char * desc
Description of mailbox.
Definition: lib.h:82
int msg_count
total number of messages
Definition: lib.h:85
bool inferiors
Folder has children.
Definition: lib.h:93
int msg_unread
number of unread messages
Definition: lib.h:86
IMAP-specific Account data -.
Definition: adata.h:40
char delim
Path delimiter.
Definition: adata.h:75
struct ImapList * cmdresult
Definition: adata.h:66
bool unicode
If true, we can send UTF-8, and the server will use UTF8 rather than mUTF7.
Definition: adata.h:62
ImapCapFlags capabilities
Capability flags.
Definition: adata.h:55
char * buf
Definition: adata.h:59
struct Connection * conn
Connection to IMAP server.
Definition: adata.h:41
Items in an IMAP browser.
Definition: private.h:151
bool noselect
Definition: private.h:154
bool noinferiors
Definition: private.h:155
char * name
Definition: private.h:152
char delim
Definition: private.h:153
IMAP-specific Mailbox data -.
Definition: mdata.h:39
char * real_name
Original Mailbox name, e.g.: INBOX can be just \0.
Definition: mdata.h:42
char * name
Mailbox name.
Definition: mdata.h:40
List of Mailboxes.
Definition: mailbox.h:152
struct Mailbox * mailbox
Mailbox in the list.
Definition: mailbox.h:153
bool has_new
Mailbox has new mail.
Definition: mailbox.h:85
int msg_count
Total number of messages.
Definition: mailbox.h:88
void * mdata
Driver specific data.
Definition: mailbox.h:132
int msg_unread
Number of unread messages.
Definition: mailbox.h:89
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Cached regular expression.
Definition: regex3.h:89
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:69
char * path
Path.
Definition: url.h:75
int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition: subset.c:310
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:238
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123