/src/neomutt/imap/browse.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * GUI select an IMAP mailbox from a list |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 1996-1999 Brandon Long <blong@fiction.net> |
7 | | * Copyright (C) 1999-2008 Brendan Cully <brendan@kublai.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 imap_browse Mailbox browser |
26 | | * |
27 | | * GUI select an IMAP mailbox from a list |
28 | | */ |
29 | | |
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 | | |
50 | | /** |
51 | | * add_folder - Format and add an IMAP folder to the browser |
52 | | * @param delim Path delimiter |
53 | | * @param folder Name of the folder |
54 | | * @param noselect true if item isn't selectable |
55 | | * @param noinferiors true if item has no children |
56 | | * @param state Browser state to add to |
57 | | * @param isparent true if item represents the parent folder |
58 | | * |
59 | | * The folder parameter should already be 'unmunged' via |
60 | | * imap_unmunge_mbox_name(). |
61 | | */ |
62 | | static void add_folder(char delim, char *folder, bool noselect, bool noinferiors, |
63 | | struct BrowserState *state, bool isparent) |
64 | 0 | { |
65 | 0 | char tmp[PATH_MAX] = { 0 }; |
66 | 0 | char relpath[PATH_MAX] = { 0 }; |
67 | 0 | struct ConnAccount cac = { { 0 } }; |
68 | 0 | char mailbox[1024] = { 0 }; |
69 | 0 | struct FolderFile ff = { 0 }; |
70 | |
|
71 | 0 | if (imap_parse_path(state->folder, &cac, mailbox, sizeof(mailbox))) |
72 | 0 | return; |
73 | | |
74 | 0 | if (isparent) |
75 | 0 | { |
76 | | /* render superiors as unix-standard ".." */ |
77 | 0 | mutt_str_copy(relpath, "../", sizeof(relpath)); |
78 | 0 | } |
79 | 0 | else if (mutt_str_startswith(folder, mailbox)) |
80 | 0 | { |
81 | | /* strip current folder from target, to render a relative path */ |
82 | 0 | mutt_str_copy(relpath, folder + mutt_str_len(mailbox), sizeof(relpath)); |
83 | 0 | } |
84 | 0 | else |
85 | 0 | { |
86 | 0 | mutt_str_copy(relpath, folder, sizeof(relpath)); |
87 | 0 | } |
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 | 0 | const struct Regex *c_mask = cs_subset_regex(NeoMutt->sub, "mask"); |
93 | 0 | if (!mutt_regex_match(c_mask, relpath)) |
94 | 0 | { |
95 | 0 | return; |
96 | 0 | } |
97 | | |
98 | 0 | imap_qualify_path(tmp, sizeof(tmp), &cac, folder); |
99 | 0 | ff.name = mutt_str_dup(tmp); |
100 | | |
101 | | /* mark desc with delim in browser if it can have subfolders */ |
102 | 0 | if (!isparent && !noinferiors && (strlen(relpath) < sizeof(relpath) - 1)) |
103 | 0 | { |
104 | 0 | relpath[strlen(relpath) + 1] = '\0'; |
105 | 0 | relpath[strlen(relpath)] = delim; |
106 | 0 | } |
107 | |
|
108 | 0 | ff.desc = mutt_str_dup(relpath); |
109 | 0 | ff.imap = true; |
110 | | |
111 | | /* delimiter at the root is useless. */ |
112 | 0 | if (folder[0] == '\0') |
113 | 0 | delim = '\0'; |
114 | 0 | ff.delim = delim; |
115 | 0 | ff.selectable = !noselect; |
116 | 0 | ff.inferiors = !noinferiors; |
117 | |
|
118 | 0 | struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml); |
119 | 0 | neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY); |
120 | 0 | struct MailboxNode *np = NULL; |
121 | 0 | STAILQ_FOREACH(np, &ml, entries) |
122 | 0 | { |
123 | 0 | if (mutt_str_equal(tmp, mailbox_path(np->mailbox))) |
124 | 0 | break; |
125 | 0 | } |
126 | |
|
127 | 0 | if (np) |
128 | 0 | { |
129 | 0 | ff.has_mailbox = true; |
130 | 0 | ff.has_new_mail = np->mailbox->has_new; |
131 | 0 | ff.msg_count = np->mailbox->msg_count; |
132 | 0 | ff.msg_unread = np->mailbox->msg_unread; |
133 | 0 | } |
134 | 0 | neomutt_mailboxlist_clear(&ml); |
135 | |
|
136 | 0 | ARRAY_ADD(&state->entry, ff); |
137 | 0 | } |
138 | | |
139 | | /** |
140 | | * browse_add_list_result - Add entries to the folder browser |
141 | | * @param adata Imap Account data |
142 | | * @param cmd Command string from server |
143 | | * @param bstate Browser state to add to |
144 | | * @param isparent Is this a shortcut for the parent directory? |
145 | | * @retval 0 Success |
146 | | * @retval -1 Failure |
147 | | */ |
148 | | static int browse_add_list_result(struct ImapAccountData *adata, const char *cmd, |
149 | | struct BrowserState *bstate, bool isparent) |
150 | 0 | { |
151 | 0 | struct ImapList list = { 0 }; |
152 | 0 | int rc; |
153 | 0 | struct Url *url = url_parse(bstate->folder); |
154 | 0 | if (!url) |
155 | 0 | return -1; |
156 | | |
157 | 0 | imap_cmd_start(adata, cmd); |
158 | 0 | adata->cmdresult = &list; |
159 | 0 | do |
160 | 0 | { |
161 | 0 | list.name = NULL; |
162 | 0 | rc = imap_cmd_step(adata); |
163 | |
|
164 | 0 | if ((rc == IMAP_RES_CONTINUE) && list.name) |
165 | 0 | { |
166 | | /* Let a parent folder never be selectable for navigation */ |
167 | 0 | if (isparent) |
168 | 0 | list.noselect = true; |
169 | | /* prune current folder from output */ |
170 | 0 | if (isparent || !mutt_str_startswith(url->path, list.name)) |
171 | 0 | add_folder(list.delim, list.name, list.noselect, list.noinferiors, bstate, isparent); |
172 | 0 | } |
173 | 0 | } while (rc == IMAP_RES_CONTINUE); |
174 | 0 | adata->cmdresult = NULL; |
175 | |
|
176 | 0 | url_free(&url); |
177 | |
|
178 | 0 | return (rc == IMAP_RES_OK) ? 0 : -1; |
179 | 0 | } |
180 | | |
181 | | /** |
182 | | * imap_browse - IMAP hook into the folder browser |
183 | | * @param path Current folder |
184 | | * @param state BrowserState to populate |
185 | | * @retval 0 Success |
186 | | * @retval -1 Failure |
187 | | * |
188 | | * Fill out browser_state, given a current folder to browse |
189 | | */ |
190 | | int imap_browse(const char *path, struct BrowserState *state) |
191 | 0 | { |
192 | 0 | struct ImapAccountData *adata = NULL; |
193 | 0 | struct ImapList list = { 0 }; |
194 | 0 | struct ConnAccount cac = { { 0 } }; |
195 | 0 | char buf[PATH_MAX + 16]; |
196 | 0 | char mbox[PATH_MAX] = { 0 }; |
197 | 0 | char munged_mbox[PATH_MAX] = { 0 }; |
198 | 0 | const char *list_cmd = NULL; |
199 | 0 | int len; |
200 | 0 | int n; |
201 | 0 | char ctmp; |
202 | 0 | bool showparents = false; |
203 | |
|
204 | 0 | if (imap_parse_path(path, &cac, buf, sizeof(buf))) |
205 | 0 | { |
206 | 0 | mutt_error(_("%s is an invalid IMAP path"), path); |
207 | 0 | return -1; |
208 | 0 | } |
209 | | |
210 | 0 | const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed"); |
211 | 0 | cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed", false, NULL); |
212 | | |
213 | | // Pick first mailbox connected to the same server |
214 | 0 | struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml); |
215 | 0 | neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_IMAP); |
216 | 0 | struct MailboxNode *np = NULL; |
217 | 0 | STAILQ_FOREACH(np, &ml, entries) |
218 | 0 | { |
219 | 0 | adata = imap_adata_get(np->mailbox); |
220 | | // Pick first mailbox connected on the same server |
221 | 0 | if (imap_account_match(&adata->conn->account, &cac)) |
222 | 0 | break; |
223 | 0 | adata = NULL; |
224 | 0 | } |
225 | 0 | neomutt_mailboxlist_clear(&ml); |
226 | 0 | if (!adata) |
227 | 0 | goto fail; |
228 | | |
229 | 0 | const bool c_imap_list_subscribed = cs_subset_bool(NeoMutt->sub, "imap_list_subscribed"); |
230 | 0 | if (c_imap_list_subscribed) |
231 | 0 | { |
232 | | /* RFC3348 section 3 states LSUB is unreliable for hierarchy information. |
233 | | * The newer LIST extensions are designed for this. */ |
234 | 0 | if (adata->capabilities & IMAP_CAP_LIST_EXTENDED) |
235 | 0 | list_cmd = "LIST (SUBSCRIBED RECURSIVEMATCH)"; |
236 | 0 | else |
237 | 0 | list_cmd = "LSUB"; |
238 | 0 | } |
239 | 0 | else |
240 | 0 | { |
241 | 0 | list_cmd = "LIST"; |
242 | 0 | } |
243 | |
|
244 | 0 | mutt_message(_("Getting folder list...")); |
245 | | |
246 | | /* skip check for parents when at the root */ |
247 | 0 | if (buf[0] == '\0') |
248 | 0 | { |
249 | 0 | mbox[0] = '\0'; |
250 | 0 | n = 0; |
251 | 0 | } |
252 | 0 | else |
253 | 0 | { |
254 | 0 | imap_fix_path(adata->delim, buf, mbox, sizeof(mbox)); |
255 | 0 | n = mutt_str_len(mbox); |
256 | 0 | } |
257 | |
|
258 | 0 | if (n) |
259 | 0 | { |
260 | 0 | int rc; |
261 | 0 | 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 | 0 | imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), mbox); |
266 | 0 | len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox); |
267 | 0 | if (adata->capabilities & IMAP_CAP_LIST_EXTENDED) |
268 | 0 | snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)"); |
269 | 0 | imap_cmd_start(adata, buf); |
270 | 0 | adata->cmdresult = &list; |
271 | 0 | do |
272 | 0 | { |
273 | 0 | list.name = 0; |
274 | 0 | rc = imap_cmd_step(adata); |
275 | 0 | if ((rc == IMAP_RES_CONTINUE) && list.name) |
276 | 0 | { |
277 | 0 | if (!list.noinferiors && list.name[0] && |
278 | 0 | (imap_mxcmp(list.name, mbox) == 0) && (n < sizeof(mbox) - 1)) |
279 | 0 | { |
280 | 0 | mbox[n++] = list.delim; |
281 | 0 | mbox[n] = '\0'; |
282 | 0 | } |
283 | 0 | } |
284 | 0 | } while (rc == IMAP_RES_CONTINUE); |
285 | 0 | adata->cmdresult = NULL; |
286 | | |
287 | | /* if we're descending a folder, mark it as current in browser_state */ |
288 | 0 | if (mbox[n - 1] == list.delim) |
289 | 0 | { |
290 | 0 | showparents = true; |
291 | 0 | imap_qualify_path(buf, sizeof(buf), &cac, mbox); |
292 | 0 | state->folder = mutt_str_dup(buf); |
293 | 0 | n--; |
294 | 0 | } |
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 | 0 | for (n--; n >= 0 && mbox[n] != list.delim; n--) |
303 | 0 | ; // do nothing |
304 | |
|
305 | 0 | if (n > 0) /* "aaaa/bbbb/" -> "aaaa" */ |
306 | 0 | { |
307 | | /* forget the check, it is too delicate (see above). Have we ever |
308 | | * had the parent not exist? */ |
309 | 0 | ctmp = mbox[n]; |
310 | 0 | mbox[n] = '\0'; |
311 | |
|
312 | 0 | if (showparents) |
313 | 0 | { |
314 | 0 | mutt_debug(LL_DEBUG3, "adding parent %s\n", mbox); |
315 | 0 | add_folder(list.delim, mbox, true, false, state, true); |
316 | 0 | } |
317 | | |
318 | | /* if our target isn't a folder, we are in our superior */ |
319 | 0 | if (!state->folder) |
320 | 0 | { |
321 | | /* store folder with delimiter */ |
322 | 0 | mbox[n++] = ctmp; |
323 | 0 | ctmp = mbox[n]; |
324 | 0 | mbox[n] = '\0'; |
325 | 0 | imap_qualify_path(buf, sizeof(buf), &cac, mbox); |
326 | 0 | state->folder = mutt_str_dup(buf); |
327 | 0 | } |
328 | 0 | mbox[n] = ctmp; |
329 | 0 | } |
330 | 0 | else |
331 | 0 | { |
332 | | /* "/bbbb/" -> add "/", "aaaa/" -> add "" */ |
333 | 0 | char relpath[2] = { 0 }; |
334 | | /* folder may be "/" */ |
335 | 0 | snprintf(relpath, sizeof(relpath), "%c", (n < 0) ? '\0' : adata->delim); |
336 | 0 | if (showparents) |
337 | 0 | add_folder(adata->delim, relpath, true, false, state, true); |
338 | 0 | if (!state->folder) |
339 | 0 | { |
340 | 0 | imap_qualify_path(buf, sizeof(buf), &cac, relpath); |
341 | 0 | state->folder = mutt_str_dup(buf); |
342 | 0 | } |
343 | 0 | } |
344 | 0 | } |
345 | | |
346 | | /* no namespace, no folder: set folder to host only */ |
347 | 0 | if (!state->folder) |
348 | 0 | { |
349 | 0 | imap_qualify_path(buf, sizeof(buf), &cac, NULL); |
350 | 0 | state->folder = mutt_str_dup(buf); |
351 | 0 | } |
352 | |
|
353 | 0 | mutt_debug(LL_DEBUG3, "Quoting mailbox scan: %s -> ", mbox); |
354 | 0 | snprintf(buf, sizeof(buf), "%s%%", mbox); |
355 | 0 | imap_munge_mbox_name(adata->unicode, munged_mbox, sizeof(munged_mbox), buf); |
356 | 0 | mutt_debug(LL_DEBUG3, "%s\n", munged_mbox); |
357 | 0 | len = snprintf(buf, sizeof(buf), "%s \"\" %s", list_cmd, munged_mbox); |
358 | 0 | if (adata->capabilities & IMAP_CAP_LIST_EXTENDED) |
359 | 0 | snprintf(buf + len, sizeof(buf) - len, " RETURN (CHILDREN)"); |
360 | 0 | if (browse_add_list_result(adata, buf, state, false)) |
361 | 0 | goto fail; |
362 | | |
363 | 0 | if (ARRAY_EMPTY(&state->entry)) |
364 | 0 | { |
365 | | // L10N: (%s) is the name / path of the folder we were trying to browse |
366 | 0 | mutt_error(_("No such folder: %s"), path); |
367 | 0 | goto fail; |
368 | 0 | } |
369 | | |
370 | 0 | mutt_clear_error(); |
371 | |
|
372 | 0 | cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed", |
373 | 0 | c_imap_check_subscribed, NULL); |
374 | 0 | return 0; |
375 | | |
376 | 0 | fail: |
377 | 0 | cs_subset_str_native_set(NeoMutt->sub, "imap_check_subscribed", |
378 | 0 | c_imap_check_subscribed, NULL); |
379 | 0 | return -1; |
380 | 0 | } |
381 | | |
382 | | /** |
383 | | * imap_mailbox_create - Create a new IMAP mailbox |
384 | | * @param path Mailbox to create |
385 | | * @retval 0 Success |
386 | | * @retval -1 Failure |
387 | | * |
388 | | * Prompt for a new mailbox name, and try to create it |
389 | | */ |
390 | | int imap_mailbox_create(const char *path) |
391 | 0 | { |
392 | 0 | struct ImapAccountData *adata = NULL; |
393 | 0 | struct ImapMboxData *mdata = NULL; |
394 | 0 | struct Buffer *name = buf_pool_get(); |
395 | 0 | int rc = -1; |
396 | |
|
397 | 0 | if (imap_adata_find(path, &adata, &mdata) < 0) |
398 | 0 | { |
399 | 0 | mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path); |
400 | 0 | goto done; |
401 | 0 | } |
402 | | |
403 | | /* append a delimiter if necessary */ |
404 | 0 | const size_t n = buf_strcpy(name, mdata->real_name); |
405 | 0 | if ((n != 0) && (name->data[n - 1] != adata->delim)) |
406 | 0 | { |
407 | 0 | buf_addch(name, adata->delim); |
408 | 0 | } |
409 | |
|
410 | 0 | if (buf_get_field(_("Create mailbox: "), name, MUTT_COMP_FILE, false, NULL, NULL, NULL) != 0) |
411 | 0 | { |
412 | 0 | goto done; |
413 | 0 | } |
414 | | |
415 | 0 | if (buf_is_empty(name)) |
416 | 0 | { |
417 | 0 | mutt_error(_("Mailbox must have a name")); |
418 | 0 | goto done; |
419 | 0 | } |
420 | | |
421 | 0 | if (imap_create_mailbox(adata, buf_string(name)) < 0) |
422 | 0 | goto done; |
423 | | |
424 | 0 | imap_mdata_free((void *) &mdata); |
425 | 0 | mutt_message(_("Mailbox created")); |
426 | 0 | mutt_sleep(0); |
427 | 0 | rc = 0; |
428 | |
|
429 | 0 | done: |
430 | 0 | imap_mdata_free((void *) &mdata); |
431 | 0 | buf_pool_release(&name); |
432 | 0 | return rc; |
433 | 0 | } |
434 | | |
435 | | /** |
436 | | * imap_mailbox_rename - Rename a mailbox |
437 | | * @param path Mailbox to rename |
438 | | * @retval 0 Success |
439 | | * @retval -1 Failure |
440 | | * |
441 | | * The user will be prompted for a new name. |
442 | | */ |
443 | | int imap_mailbox_rename(const char *path) |
444 | 0 | { |
445 | 0 | struct ImapAccountData *adata = NULL; |
446 | 0 | struct ImapMboxData *mdata = NULL; |
447 | 0 | struct Buffer *buf = NULL; |
448 | 0 | struct Buffer *newname = NULL; |
449 | 0 | int rc = -1; |
450 | |
|
451 | 0 | if (imap_adata_find(path, &adata, &mdata) < 0) |
452 | 0 | { |
453 | 0 | mutt_debug(LL_DEBUG1, "Couldn't find open connection to %s\n", path); |
454 | 0 | goto done; |
455 | 0 | } |
456 | | |
457 | 0 | if (mdata->real_name[0] == '\0') |
458 | 0 | { |
459 | 0 | mutt_error(_("Can't rename root folder")); |
460 | 0 | goto done; |
461 | 0 | } |
462 | | |
463 | 0 | buf = buf_pool_get(); |
464 | 0 | newname = buf_pool_get(); |
465 | |
|
466 | 0 | buf_printf(buf, _("Rename mailbox %s to: "), mdata->name); |
467 | 0 | buf_strcpy(newname, mdata->name); |
468 | |
|
469 | 0 | if (buf_get_field(buf_string(buf), newname, MUTT_COMP_FILE, false, NULL, NULL, NULL) != 0) |
470 | 0 | { |
471 | 0 | goto done; |
472 | 0 | } |
473 | | |
474 | 0 | if (buf_is_empty(newname)) |
475 | 0 | { |
476 | 0 | mutt_error(_("Mailbox must have a name")); |
477 | 0 | goto done; |
478 | 0 | } |
479 | | |
480 | 0 | imap_fix_path(adata->delim, buf_string(newname), buf->data, buf->dsize); |
481 | |
|
482 | 0 | if (imap_rename_mailbox(adata, mdata->name, buf_string(buf)) < 0) |
483 | 0 | { |
484 | 0 | mutt_error(_("Rename failed: %s"), imap_get_qualifier(adata->buf)); |
485 | 0 | goto done; |
486 | 0 | } |
487 | | |
488 | 0 | mutt_message(_("Mailbox renamed")); |
489 | 0 | mutt_sleep(0); |
490 | 0 | rc = 0; |
491 | |
|
492 | 0 | done: |
493 | 0 | imap_mdata_free((void *) &mdata); |
494 | 0 | buf_pool_release(&buf); |
495 | 0 | buf_pool_release(&newname); |
496 | |
|
497 | 0 | return rc; |
498 | 0 | } |