NeoMutt  2022-04-29-215-gc12b98
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 /* render superiors as unix-standard ".." */
75 if (isparent)
76 mutt_str_copy(relpath, "../", sizeof(relpath));
77 /* strip current folder from target, to render a relative path */
78 else if (mutt_str_startswith(folder, mailbox))
79 mutt_str_copy(relpath, folder + mutt_str_len(mailbox), sizeof(relpath));
80 else
81 mutt_str_copy(relpath, folder, sizeof(relpath));
82
83 /* apply filemask filter. This should really be done at menu setup rather
84 * than at scan, since it's so expensive to scan. But that's big changes
85 * to browser.c */
86 const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask");
87 if (!mutt_regex_match(c_mask, relpath))
88 {
89 return;
90 }
91
92 imap_qualify_path(tmp, sizeof(tmp), &cac, folder);
93 ff.name = mutt_str_dup(tmp);
94
95 /* mark desc with delim in browser if it can have subfolders */
96 if (!isparent && !noinferiors && (strlen(relpath) < sizeof(relpath) - 1))
97 {
98 relpath[strlen(relpath) + 1] = '\0';
99 relpath[strlen(relpath)] = delim;
100 }
101
102 ff.desc = mutt_str_dup(relpath);
103 ff.imap = true;
104
105 /* delimiter at the root is useless. */
106 if (folder[0] == '\0')
107 delim = '\0';
108 ff.delim = delim;
109 ff.selectable = !noselect;
110 ff.inferiors = !noinferiors;
111
112 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
114 struct MailboxNode *np = NULL;
115 STAILQ_FOREACH(np, &ml, entries)
116 {
117 if (mutt_str_equal(tmp, mailbox_path(np->mailbox)))
118 break;
119 }
120
121 if (np)
122 {
123 ff.has_mailbox = true;
124 ff.has_new_mail = np->mailbox->has_new;
125 ff.msg_count = np->mailbox->msg_count;
126 ff.msg_unread = np->mailbox->msg_unread;
127 }
129
130 ARRAY_ADD(&state->entry, ff);
131}
132
142static int browse_add_list_result(struct ImapAccountData *adata, const char *cmd,
143 struct BrowserState *state, bool isparent)
144{
145 struct ImapList list = { 0 };
146 int rc;
147 struct Url *url = url_parse(state->folder);
148
149 imap_cmd_start(adata, cmd);
150 adata->cmdresult = &list;
151 do
152 {
153 list.name = NULL;
154 rc = imap_cmd_step(adata);
155
156 if ((rc == IMAP_RES_CONTINUE) && list.name)
157 {
158 /* Let a parent folder never be selectable for navigation */
159 if (isparent)
160 list.noselect = true;
161 /* prune current folder from output */
162 if (isparent || !mutt_str_startswith(url->path, list.name))
163 add_folder(list.delim, list.name, list.noselect, list.noinferiors, state, isparent);
164 }
165 } while (rc == IMAP_RES_CONTINUE);
166 adata->cmdresult = NULL;
167
168 url_free(&url);
169
170 return (rc == IMAP_RES_OK) ? 0 : -1;
171}
172
182int imap_browse(const char *path, struct BrowserState *state)
183{
184 struct ImapAccountData *adata = NULL;
185 struct ImapList list = { 0 };
186 struct ConnAccount cac = { { 0 } };
187 char buf[PATH_MAX + 16];
188 char mbox[PATH_MAX] = { 0 };
189 char munged_mbox[PATH_MAX] = { 0 };
190 const char *list_cmd = NULL;
191 int len;
192 int n;
193 char ctmp;
194 bool showparents = false;
195
196 if (imap_parse_path(path, &cac, buf, sizeof(buf)))
197 {
198 mutt_error(_("%s is an invalid IMAP path"), path);
199 return -1;
200 }
201
202 const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
203 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed", false, NULL);
204
205 // Pick first mailbox connected to the same server
206 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
208 struct MailboxNode *np = NULL;
209 STAILQ_FOREACH(np, &ml, entries)
210 {
211 adata = imap_adata_get(np->mailbox);
212 // Pick first mailbox connected on the same server
213 if (imap_account_match(&adata->conn->account, &cac))
214 break;
215 adata = NULL;
216 }
218 if (!adata)
219 goto fail;
220
221 const bool c_imap_list_subscribed = cs_subset_bool(NeoMutt->sub, "imap_list_subscribed");
222 if (c_imap_list_subscribed)
223 {
224 /* RFC3348 section 3 states LSUB is unreliable for hierarchy information.
225 * The newer LIST extensions are designed for this. */
227 list_cmd = "LIST (SUBSCRIBED RECURSIVEMATCH)";
228 else
229 list_cmd = "LSUB";
230 }
231 else
232 {
233 list_cmd = "LIST";
234 }
235
236 mutt_message(_("Getting folder list..."));
237
238 /* skip check for parents when at the root */
239 if (buf[0] == '\0')
240 {
241 mbox[0] = '\0';
242 n = 0;
243 }
244 else
245 {
246 imap_fix_path(adata->delim, buf, mbox, sizeof(mbox));
247 n = mutt_str_len(mbox);
248 }
249
250 if (n)
251 {
252 int rc;
253 mutt_debug(LL_DEBUG3, "mbox: %s\n", mbox);
254
255 /* if our target exists and has inferiors, enter it if we
256 * aren't already going to */
257 imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), mbox);
258 len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
260 snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
261 imap_cmd_start(adata, buf);
262 adata->cmdresult = &list;
263 do
264 {
265 list.name = 0;
266 rc = imap_cmd_step(adata);
267 if ((rc == IMAP_RES_CONTINUE) && list.name)
268 {
269 if (!list.noinferiors && list.name[0] &&
270 (imap_mxcmp(list.name, mbox) == 0) && (n < sizeof(mbox) - 1))
271 {
272 mbox[n++] = list.delim;
273 mbox[n] = '\0';
274 }
275 }
276 } while (rc == IMAP_RES_CONTINUE);
277 adata->cmdresult = NULL;
278
279 /* if we're descending a folder, mark it as current in browser_state */
280 if (mbox[n - 1] == list.delim)
281 {
282 showparents = true;
283 imap_qualify_path(buf, sizeof(buf), &cac, mbox);
284 state->folder = mutt_str_dup(buf);
285 n--;
286 }
287
288 /* Find superiors to list
289 * Note: UW-IMAP servers return folder + delimiter when asked to list
290 * folder + delimiter. Cyrus servers don't. So we ask for folder,
291 * and tack on delimiter ourselves.
292 * Further note: UW-IMAP servers return nothing when asked for
293 * NAMESPACES without delimiters at the end. Argh! */
294 for (n--; n >= 0 && mbox[n] != list.delim; n--)
295 ; // do nothing
296
297 if (n > 0) /* "aaaa/bbbb/" -> "aaaa" */
298 {
299 /* forget the check, it is too delicate (see above). Have we ever
300 * had the parent not exist? */
301 ctmp = mbox[n];
302 mbox[n] = '\0';
303
304 if (showparents)
305 {
306 mutt_debug(LL_DEBUG3, "adding parent %s\n", mbox);
307 add_folder(list.delim, mbox, true, false, state, true);
308 }
309
310 /* if our target isn't a folder, we are in our superior */
311 if (!state->folder)
312 {
313 /* store folder with delimiter */
314 mbox[n++] = ctmp;
315 ctmp = mbox[n];
316 mbox[n] = '\0';
317 imap_qualify_path(buf, sizeof(buf), &cac, mbox);
318 state->folder = mutt_str_dup(buf);
319 }
320 mbox[n] = ctmp;
321 }
322 /* "/bbbb/" -> add "/", "aaaa/" -> add "" */
323 else
324 {
325 char relpath[2] = { 0 };
326 /* folder may be "/" */
327 snprintf(relpath, sizeof(relpath), "%c", (n < 0) ? '\0' : adata->delim);
328 if (showparents)
329 add_folder(adata->delim, relpath, true, false, state, true);
330 if (!state->folder)
331 {
332 imap_qualify_path(buf, sizeof(buf), &cac, relpath);
333 state->folder = mutt_str_dup(buf);
334 }
335 }
336 }
337
338 /* no namespace, no folder: set folder to host only */
339 if (!state->folder)
340 {
341 imap_qualify_path(buf, sizeof(buf), &cac, NULL);
342 state->folder = mutt_str_dup(buf);
343 }
344
345 mutt_debug(LL_DEBUG3, "Quoting mailbox scan: %s -> ", mbox);
346 snprintf(buf, sizeof(buf), "%s%%", mbox);
347 imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), buf);
348 mutt_debug(LL_DEBUG3, "%s\n", munged_mbox);
349 len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
351 snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
352 if (browse_add_list_result(adata, buf, state, false))
353 goto fail;
354
355 if (ARRAY_EMPTY(&state->entry))
356 {
357 // L10N: (%s) is the name / path of the folder we were trying to browse
358 mutt_error(_("No such folder: %s"), path);
359 goto fail;
360 }
361
363
364 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
365 c_imap_check_subscribed, NULL);
366 return 0;
367
368fail:
369 cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
370 c_imap_check_subscribed, NULL);
371 return -1;
372}
373
382int imap_mailbox_create(const char *path)
383{
384 struct ImapAccountData *adata = NULL;
385 struct ImapMboxData *mdata = NULL;
386 struct Buffer *name = mutt_buffer_pool_get();
387 int rc = -1;
388
389 if (imap_adata_find(path, &adata, &mdata) < 0)
390 {
391 mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path);
392 goto done;
393 }
394
395 /* append a delimiter if necessary */
396 const size_t n = mutt_buffer_strcpy(name, mdata->real_name);
397 if ((n != 0) && (name->data[n - 1] != adata->delim))
398 {
399 mutt_buffer_addch(name, adata->delim);
400 }
401
402 if (mutt_buffer_get_field(_("Create mailbox: "), name, MUTT_COMP_FILE, false,
403 NULL, NULL, NULL) != 0)
404 {
405 goto done;
406 }
407
408 if (mutt_buffer_is_empty(name))
409 {
410 mutt_error(_("Mailbox must have a name"));
411 goto done;
412 }
413
414 if (imap_create_mailbox(adata, mutt_buffer_string(name)) < 0)
415 goto done;
416
417 imap_mdata_free((void *) &mdata);
418 mutt_message(_("Mailbox created"));
419 mutt_sleep(0);
420 rc = 0;
421
422done:
423 imap_mdata_free((void *) &mdata);
425 return rc;
426}
427
436int imap_mailbox_rename(const char *path)
437{
438 struct ImapAccountData *adata = NULL;
439 struct ImapMboxData *mdata = NULL;
440 struct Buffer *buf = NULL;
441 struct Buffer *newname = NULL;
442 int rc = -1;
443
444 if (imap_adata_find(path, &adata, &mdata) < 0)
445 {
446 mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path);
447 goto done;
448 }
449
450 if (mdata->real_name[0] == '\0')
451 {
452 mutt_error(_("Can't rename root folder"));
453 goto done;
454 }
455
456 buf = mutt_buffer_pool_get();
457 newname = mutt_buffer_pool_get();
458
459 mutt_buffer_printf(buf, _("Rename mailbox %s to: "), mdata->name);
460 mutt_buffer_strcpy(newname, mdata->name);
461
463 false, NULL, NULL, NULL) != 0)
464 {
465 goto done;
466 }
467
468 if (mutt_buffer_is_empty(newname))
469 {
470 mutt_error(_("Mailbox must have a name"));
471 goto done;
472 }
473
474 imap_fix_path(adata->delim, mutt_buffer_string(newname), buf->data, buf->dsize);
475
476 if (imap_rename_mailbox(adata, mdata->name, mutt_buffer_string(buf)) < 0)
477 {
478 mutt_error(_("Rename failed: %s"), imap_get_qualifier(adata->buf));
479 goto done;
480 }
481
482 mutt_message(_("Mailbox renamed"));
483 mutt_sleep(0);
484 rc = 0;
485
486done:
487 imap_mdata_free((void *) &mdata);
489 mutt_buffer_pool_release(&newname);
490
491 return rc;
492}
#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.
bool mutt_buffer_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:260
size_t mutt_buffer_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:327
size_t mutt_buffer_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:248
int mutt_buffer_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:168
static const char * mutt_buffer_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:77
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 mutt_buffer_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: logging.h:87
#define mutt_message(...)
Definition: logging.h:86
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
struct ImapAccountData * imap_adata_get(struct Mailbox *m)
Get the Account data for this mailbox.
Definition: adata.c:89
int imap_browse(const char *path, struct BrowserState *state)
IMAP hook into the folder browser.
Definition: browse.c:182
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:382
int imap_mailbox_rename(const char *path)
Rename a mailbox.
Definition: browse.c:436
static int browse_add_list_result(struct ImapAccountData *adata, const char *cmd, struct BrowserState *state, bool isparent)
Add entries to the folder browser.
Definition: browse.c:142
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
Given an IMAP command, send it to the server.
Definition: command.c:1062
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition: command.c:1076
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:482
int imap_mxcmp(const char *mx1, const char *mx2)
Compare mailbox names, giving priority to INBOX.
Definition: util.c:553
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:820
#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:685
#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:72
bool imap_account_match(const struct ConnAccount *a1, const struct ConnAccount *a2)
Compare two Accounts.
Definition: util.c:1044
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:911
#define IMAP_RES_CONTINUE
* ...
Definition: private.h:57
char * imap_get_qualifier(char *buf)
Get the qualifier from a tagged response.
Definition: util.c:772
int imap_rename_mailbox(struct ImapAccountData *adata, char *oldname, const char *newname)
Rename a mailbox.
Definition: imap.c:488
int imap_create_mailbox(struct ImapAccountData *adata, const char *mailbox)
Create a new mailbox.
Definition: imap.c:447
@ LL_DEBUG3
Log at debug level 3.
Definition: logging.h:42
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:210
@ 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:631
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:807
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:567
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:652
Many unsorted constants and some structs.
#define MUTT_COMP_FILE
File completion (in browser)
Definition: mutt.h:55
#define PATH_MAX
Definition: mutt.h:40
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:74
NeoMutt Logging.
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1455
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.
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
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:150
bool noselect
Definition: private.h:153
bool noinferiors
Definition: private.h:154
char * name
Definition: private.h:151
char delim
Definition: private.h:152
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:153
struct Mailbox * mailbox
Mailbox in the list.
Definition: mailbox.h:154
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:305
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123