NeoMutt  2021-02-05-666-ge300cd
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 "gui/lib.h"
42 #include "mutt.h"
43 #include "lib.h"
44 #include "adata.h"
45 #include "browser.h"
46 #include "mdata.h"
47 #include "mutt_logging.h"
48 #include "muttlib.h"
49 
62 static void add_folder(char delim, char *folder, bool noselect, bool noinferiors,
63  struct BrowserState *state, bool isparent)
64 {
65  char tmp[PATH_MAX];
66  char relpath[PATH_MAX];
67  struct ConnAccount cac = { { 0 } };
68  char mailbox[1024];
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 
142 static 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 
182 int 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];
189  char munged_mbox[PATH_MAX];
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 =
203  cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
204  cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed", false, NULL);
205 
206  // Pick first mailbox connected to the same server
207  struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
209  struct MailboxNode *np = NULL;
210  STAILQ_FOREACH(np, &ml, entries)
211  {
212  adata = imap_adata_get(np->mailbox);
213  // Pick first mailbox connected on the same server
214  if (imap_account_match(&adata->conn->account, &cac))
215  break;
216  adata = NULL;
217  }
219  if (!adata)
220  goto fail;
221 
222  const bool c_imap_list_subscribed =
223  cs_subset_bool(NeoMutt->sub, "imap_list_subscribed");
224  if (c_imap_list_subscribed)
225  {
226  /* RFC3348 section 3 states LSUB is unreliable for hierarchy information.
227  * The newer LIST extensions are designed for this. */
229  list_cmd = "LIST (SUBSCRIBED RECURSIVEMATCH)";
230  else
231  list_cmd = "LSUB";
232  }
233  else
234  {
235  list_cmd = "LIST";
236  }
237 
238  mutt_message(_("Getting folder list..."));
239 
240  /* skip check for parents when at the root */
241  if (buf[0] == '\0')
242  {
243  mbox[0] = '\0';
244  n = 0;
245  }
246  else
247  {
248  imap_fix_path(adata->delim, buf, mbox, sizeof(mbox));
249  n = mutt_str_len(mbox);
250  }
251 
252  if (n)
253  {
254  int rc;
255  mutt_debug(LL_DEBUG3, "mbox: %s\n", mbox);
256 
257  /* if our target exists and has inferiors, enter it if we
258  * aren't already going to */
259  imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), mbox);
260  len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
262  snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
263  imap_cmd_start(adata, buf);
264  adata->cmdresult = &list;
265  do
266  {
267  list.name = 0;
268  rc = imap_cmd_step(adata);
269  if ((rc == IMAP_RES_CONTINUE) && list.name)
270  {
271  if (!list.noinferiors && list.name[0] &&
272  (imap_mxcmp(list.name, mbox) == 0) && (n < sizeof(mbox) - 1))
273  {
274  mbox[n++] = list.delim;
275  mbox[n] = '\0';
276  }
277  }
278  } while (rc == IMAP_RES_CONTINUE);
279  adata->cmdresult = NULL;
280 
281  /* if we're descending a folder, mark it as current in browser_state */
282  if (mbox[n - 1] == list.delim)
283  {
284  showparents = true;
285  imap_qualify_path(buf, sizeof(buf), &cac, mbox);
286  state->folder = mutt_str_dup(buf);
287  n--;
288  }
289 
290  /* Find superiors to list
291  * Note: UW-IMAP servers return folder + delimiter when asked to list
292  * folder + delimiter. Cyrus servers don't. So we ask for folder,
293  * and tack on delimiter ourselves.
294  * Further note: UW-IMAP servers return nothing when asked for
295  * NAMESPACES without delimiters at the end. Argh! */
296  for (n--; n >= 0 && mbox[n] != list.delim; n--)
297  ; // do nothing
298 
299  if (n > 0) /* "aaaa/bbbb/" -> "aaaa" */
300  {
301  /* forget the check, it is too delicate (see above). Have we ever
302  * had the parent not exist? */
303  ctmp = mbox[n];
304  mbox[n] = '\0';
305 
306  if (showparents)
307  {
308  mutt_debug(LL_DEBUG3, "adding parent %s\n", mbox);
309  add_folder(list.delim, mbox, true, false, state, true);
310  }
311 
312  /* if our target isn't a folder, we are in our superior */
313  if (!state->folder)
314  {
315  /* store folder with delimiter */
316  mbox[n++] = ctmp;
317  ctmp = mbox[n];
318  mbox[n] = '\0';
319  imap_qualify_path(buf, sizeof(buf), &cac, mbox);
320  state->folder = mutt_str_dup(buf);
321  }
322  mbox[n] = ctmp;
323  }
324  /* "/bbbb/" -> add "/", "aaaa/" -> add "" */
325  else
326  {
327  char relpath[2];
328  /* folder may be "/" */
329  snprintf(relpath, sizeof(relpath), "%c", (n < 0) ? '\0' : adata->delim);
330  if (showparents)
331  add_folder(adata->delim, relpath, true, false, state, true);
332  if (!state->folder)
333  {
334  imap_qualify_path(buf, sizeof(buf), &cac, relpath);
335  state->folder = mutt_str_dup(buf);
336  }
337  }
338  }
339 
340  /* no namespace, no folder: set folder to host only */
341  if (!state->folder)
342  {
343  imap_qualify_path(buf, sizeof(buf), &cac, NULL);
344  state->folder = mutt_str_dup(buf);
345  }
346 
347  mutt_debug(LL_DEBUG3, "Quoting mailbox scan: %s -> ", mbox);
348  snprintf(buf, sizeof(buf), "%s%%", mbox);
349  imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), buf);
350  mutt_debug(LL_DEBUG3, "%s\n", munged_mbox);
351  len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox);
353  snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)");
354  if (browse_add_list_result(adata, buf, state, false))
355  goto fail;
356 
357  if (ARRAY_EMPTY(&state->entry))
358  {
359  // L10N: (%s) is the name / path of the folder we were trying to browse
360  mutt_error(_("No such folder: %s"), path);
361  goto fail;
362  }
363 
365 
366  cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
367  c_imap_check_subscribed, NULL);
368  return 0;
369 
370 fail:
371  cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed",
372  c_imap_check_subscribed, NULL);
373  return -1;
374 }
375 
384 int imap_mailbox_create(const char *path)
385 {
386  struct ImapAccountData *adata = NULL;
387  struct ImapMboxData *mdata = NULL;
388  char name[1024];
389 
390  if (imap_adata_find(path, &adata, &mdata) < 0)
391  {
392  mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path);
393  goto err;
394  }
395 
396  /* append a delimiter if necessary */
397  const size_t n = mutt_str_copy(name, mdata->real_name, sizeof(name));
398  if (n && (n < sizeof(name) - 1) && (name[n - 1] != adata->delim))
399  {
400  name[n] = adata->delim;
401  name[n + 1] = '\0';
402  }
403 
404  if (mutt_get_field(_("Create mailbox: "), name, sizeof(name), MUTT_FILE,
405  false, NULL, NULL) < 0)
406  {
407  goto err;
408  }
409 
410  if (mutt_str_len(name) == 0)
411  {
412  mutt_error(_("Mailbox must have a name"));
413  goto err;
414  }
415 
416  if (imap_create_mailbox(adata, name) < 0)
417  goto err;
418 
419  imap_mdata_free((void *) &mdata);
420  mutt_message(_("Mailbox created"));
421  mutt_sleep(0);
422  return 0;
423 
424 err:
425  imap_mdata_free((void *) &mdata);
426  return -1;
427 }
428 
437 int imap_mailbox_rename(const char *path)
438 {
439  struct ImapAccountData *adata = NULL;
440  struct ImapMboxData *mdata = NULL;
441  char buf[PATH_MAX];
442  char newname[PATH_MAX];
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  return -1;
448  }
449 
450  if (mdata->real_name[0] == '\0')
451  {
452  mutt_error(_("Can't rename root folder"));
453  goto err;
454  }
455 
456  snprintf(buf, sizeof(buf), _("Rename mailbox %s to: "), mdata->name);
457  mutt_str_copy(newname, mdata->name, sizeof(newname));
458 
459  if (mutt_get_field(buf, newname, sizeof(newname), MUTT_FILE, false, NULL, NULL) < 0)
460  goto err;
461 
462  if (mutt_str_len(newname) == 0)
463  {
464  mutt_error(_("Mailbox must have a name"));
465  goto err;
466  }
467 
468  imap_fix_path(adata->delim, newname, buf, sizeof(buf));
469 
470  if (imap_rename_mailbox(adata, mdata->name, buf) < 0)
471  {
472  mutt_error(_("Rename failed: %s"), imap_get_qualifier(adata->buf));
473  goto err;
474  }
475 
476  mutt_message(_("Mailbox renamed"));
477  mutt_sleep(0);
478  return 0;
479 
480 err:
481  imap_mdata_free((void *) &mdata);
482  return -1;
483 }
int msg_unread
number of unread messages
Definition: browser.h:67
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:904
Convenience wrapper for the gui headers.
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox&#39;s path string.
Definition: mailbox.h:215
void imap_qualify_path(char *buf, size_t buflen, struct ConnAccount *conn_account, char *path)
Make an absolute IMAP folder target.
Definition: util.c:823
bool has_mailbox
Definition: browser.h:76
int msg_count
Total number of messages.
Definition: mailbox.h:91
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
#define IMAP_RES_OK
<tag> OK ...
Definition: private.h:56
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition: command.c:1079
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:40
int msg_unread
Number of unread messages.
Definition: mailbox.h:92
Structs that make up an email.
#define mutt_error(...)
Definition: logging.h:88
char * desc
Definition: browser.h:63
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:68
void neomutt_mailboxlist_clear(struct MailboxList *ml)
Free a Mailbox List.
Definition: neomutt.c:141
int mutt_get_field(const char *field, char *buf, size_t buflen, CompletionFlags complete, bool multiple, char ***files, int *numfiles)
Ask the user for a string.
Definition: curs_lib.c:335
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:483
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:914
void imap_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: mdata.c:40
NeoMutt Logging.
State of the file/mailbox browser.
Definition: browser.h:91
Match any Mailbox type.
Definition: mailbox.h:45
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
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
int imap_rename_mailbox(struct ImapAccountData *adata, char *oldname, const char *newname)
Rename a mailbox.
Definition: imap.c:489
#define _(a)
Definition: message.h:28
struct BrowserStateEntry entry
Definition: browser.h:93
int imap_mailbox_create(const char *path)
Create a new IMAP mailbox.
Definition: browse.c:384
Items in an IMAP browser.
Definition: private.h:148
bool imap_account_match(const struct ConnAccount *a1, const struct ConnAccount *a2)
Compare two Accounts.
Definition: util.c:1049
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
Imap-specific Account data.
int imap_cmd_start(struct ImapAccountData *adata, const char *cmdstr)
Given an IMAP command, send it to the server.
Definition: command.c:1065
char delim
Definition: private.h:151
Container for Accounts, Notifications.
Definition: neomutt.h:36
Convenience wrapper for the config headers.
GUI component for displaying/selecting items from a list.
char * folder
Definition: browser.h:96
bool selectable
Definition: browser.h:73
Some miscellaneous functions.
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 has_new
Mailbox has new mail.
Definition: mailbox.h:88
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
int imap_adata_find(const char *path, struct ImapAccountData **adata, struct ImapMboxData **mdata)
Find the Account data for this path.
Definition: util.c:72
Imap-specific Mailbox data.
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1461
Many unsorted constants and some structs.
Convenience wrapper for the core headers.
char * name
Definition: browser.h:62
bool imap
Definition: browser.h:72
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:112
char delim
Definition: adata.h:75
Shared constants/structs that are private to IMAP.
void * mdata
Driver specific data.
Definition: mailbox.h:136
int imap_mxcmp(const char *mx1, const char *mx2)
Compare mailbox names, giving priority to INBOX.
Definition: util.c:554
Browser entry representing a folder/dir.
Definition: browser.h:53
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:160
#define PATH_MAX
Definition: mutt.h:40
char * name
Definition: private.h:150
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
#define ARRAY_EMPTY(head)
Check if an array is empty.
Definition: array.h:70
char * name
Mailbox name.
Definition: mdata.h:40
int msg_count
total number of messages
Definition: browser.h:66
ImapCapFlags capabilities
Definition: adata.h:55
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:352
#define IMAP_CAP_LIST_EXTENDED
RFC5258: IMAP4 LIST Command Extensions.
Definition: private.h:139
struct ImapAccountData * imap_adata_get(struct Mailbox *m)
Get the Account data for this mailbox.
Definition: adata.c:90
int imap_create_mailbox(struct ImapAccountData *adata, char *mailbox)
Create a new mailbox.
Definition: imap.c:448
#define mutt_debug(LEVEL,...)
Definition: logging.h:85
Login details for a remote server.
Definition: connaccount.h:51
#define MUTT_FILE
Do file completion.
Definition: mutt.h:54
bool noinferiors
Definition: private.h:153
char * path
Path.
Definition: url.h:75
char * real_name
Original Mailbox name, e.g.: INBOX can be just \0.
Definition: mdata.h:42
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:664
IMAP-specific Account data -.
Definition: adata.h:39
char * imap_get_qualifier(char *buf)
Get the qualifier from a tagged response.
Definition: util.c:775
Log at debug level 1.
Definition: logging.h:40
char * imap_fix_path(char delim, const char *mailbox, char *path, size_t plen)
Fix up the imap path.
Definition: util.c:687
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:749
IMAP-specific Mailbox data -.
Definition: mdata.h:38
char * buf
Definition: adata.h:59
Cached regular expression.
Definition: regex3.h:89
int imap_browse(const char *path, struct BrowserState *state)
IMAP hook into the folder browser.
Definition: browse.c:182
bool mutt_regex_match(const struct Regex *regex, const char *str)
Shorthand to mutt_regex_capture()
Definition: regex.c:613
Connection Library.
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
#define mutt_message(...)
Definition: logging.h:87
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:152
int imap_mailbox_rename(const char *path)
Rename a mailbox.
Definition: browse.c:437
#define STAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:324
struct ImapList * cmdresult
Definition: adata.h:66
List of Mailboxes.
Definition: mailbox.h:156
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
Convenience wrapper for the library headers.
bool inferiors
Definition: browser.h:74
#define IMAP_RES_CONTINUE
* ...
Definition: private.h:57
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
&#39;IMAP&#39; Mailbox type
Definition: mailbox.h:53
char delim
Definition: browser.h:70
Log at debug level 3.
Definition: logging.h:42
bool noselect
Definition: private.h:152
bool unicode
If true, we can send UTF-8, and the server will use UTF8 rather than mUTF7.
Definition: adata.h:62
struct Connection * conn
Definition: adata.h:41
struct Mailbox * mailbox
Mailbox in the list.
Definition: mailbox.h:158
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
bool has_new_mail
true if mailbox has "new mail"
Definition: browser.h:65