1 /**
2 * @file
3 * Functions to parse commands in a config file
4 *
5 * @authors
6 * Copyright (C) 1996-2002,2007,2010,2012-2013,2016 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2004 g10 Code GmbH
8 * Copyright (C) 2020 R Primus <rprimus@gmail.com>
9 *
10 * @copyright
11 * This program is free software: you can redistribute it and/or modify it under
12 * the terms of the GNU General Public License as published by the Free Software
13 * Foundation, either version 2 of the License, or (at your option) any later
14 * version.
15 *
16 * This program is distributed in the hope that it will be useful, but WITHOUT
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19 * details.
20 *
21 * You should have received a copy of the GNU General Public License along with
22 * this program. If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 /**
26 * @page neo_command_parse Functions to parse commands in a config file
27 *
28 * Functions to parse commands in a config file
29 */
30
31 #include "config.h"
32 #include <assert.h>
33 #include <errno.h>
34 #include <limits.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include "mutt/lib.h"
40 #include "address/lib.h"
41 #include "config/lib.h"
42 #include "email/lib.h"
43 #include "core/lib.h"
44 #include "gui/lib.h"
45 #include "mutt.h"
46 #include "command_parse.h"
47 #include "imap/lib.h"
48 #include "menu/lib.h"
49 #include "init.h"
50 #include "keymap.h"
51 #include "monitor.h"
52 #include "mutt_commands.h"
53 #include "mutt_globals.h"
54 #include "muttlib.h"
55 #include "mx.h"
56 #include "myvar.h"
57 #include "options.h"
58 #include "version.h"
59 #ifdef ENABLE_NLS
60 #include <libintl.h>
61 #endif
62
63 /* LIFO designed to contain the list of config files that have been sourced and
64 * avoid cyclic sourcing */
65 static struct ListHead MuttrcStack = STAILQ_HEAD_INITIALIZER(MuttrcStack);
66
67 #define MAX_ERRS 128
68
69 /**
70 * enum GroupState - Type of email address group
71 */
72 enum GroupState
73 {
74 GS_NONE, ///< Group is missing an argument
75 GS_RX, ///< Entry is a regular expression
76 GS_ADDR, ///< Entry is an address
77 };
78
79 /**
80 * is_function - Is the argument a neomutt function?
81 * @param name Command name to be searched for
82 * @retval true Function found
83 * @retval false Function not found
84 */
is_function(const char * name)85 static bool is_function(const char *name)
86 {
87 for (size_t i = 0; MenuNames[i].name; i++)
88 {
89 const struct Binding *b = km_get_table(MenuNames[i].value);
90 if (!b)
91 continue;
92
93 for (int j = 0; b[j].name; j++)
94 if (mutt_str_equal(name, b[j].name))
95 return true;
96 }
97 return false;
98 }
99
100 /**
101 * parse_grouplist - Parse a group context
102 * @param gl GroupList to add to
103 * @param buf Temporary Buffer space
104 * @param s Buffer containing string to be parsed
105 * @param err Buffer for error messages
106 * @retval 0 Success
107 * @retval -1 Error
108 */
parse_grouplist(struct GroupList * gl,struct Buffer * buf,struct Buffer * s,struct Buffer * err)109 int parse_grouplist(struct GroupList *gl, struct Buffer *buf, struct Buffer *s,
110 struct Buffer *err)
111 {
112 while (mutt_istr_equal(buf->data, "-group"))
113 {
114 if (!MoreArgs(s))
115 {
116 mutt_buffer_strcpy(err, _("-group: no group name"));
117 return -1;
118 }
119
120 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
121
122 mutt_grouplist_add(gl, mutt_pattern_group(buf->data));
123
124 if (!MoreArgs(s))
125 {
126 mutt_buffer_strcpy(err, _("out of arguments"));
127 return -1;
128 }
129
130 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
131 }
132
133 return 0;
134 }
135
136 /**
137 * source_rc - Read an initialization file
138 * @param rcfile_path Path to initialization file
139 * @param err Buffer for error messages
140 * @retval <0 NeoMutt should pause to let the user know
141 */
source_rc(const char * rcfile_path,struct Buffer * err)142 int source_rc(const char *rcfile_path, struct Buffer *err)
143 {
144 int lineno = 0, rc = 0, warnings = 0;
145 enum CommandResult line_rc;
146 struct Buffer *token = NULL, *linebuf = NULL;
147 char *line = NULL;
148 char *currentline = NULL;
149 char rcfile[PATH_MAX];
150 size_t linelen = 0;
151 pid_t pid;
152
153 mutt_str_copy(rcfile, rcfile_path, sizeof(rcfile));
154
155 size_t rcfilelen = mutt_str_len(rcfile);
156 if (rcfilelen == 0)
157 return -1;
158
159 bool ispipe = rcfile[rcfilelen - 1] == '|';
160
161 if (!ispipe)
162 {
163 struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
164 if (!mutt_path_to_absolute(rcfile, np ? NONULL(np->data) : ""))
165 {
166 mutt_error(_("Error: Can't build path of '%s'"), rcfile_path);
167 return -1;
168 }
169
170 STAILQ_FOREACH(np, &MuttrcStack, entries)
171 {
172 if (mutt_str_equal(np->data, rcfile))
173 {
174 break;
175 }
176 }
177 if (np)
178 {
179 mutt_error(_("Error: Cyclic sourcing of configuration file '%s'"), rcfile);
180 return -1;
181 }
182
183 mutt_list_insert_head(&MuttrcStack, mutt_str_dup(rcfile));
184 }
185
186 mutt_debug(LL_DEBUG2, "Reading configuration file '%s'\n", rcfile);
187
188 FILE *fp = mutt_open_read(rcfile, &pid);
189 if (!fp)
190 {
191 mutt_buffer_printf(err, "%s: %s", rcfile, strerror(errno));
192 return -1;
193 }
194
195 token = mutt_buffer_pool_get();
196 linebuf = mutt_buffer_pool_get();
197
198 while ((line = mutt_file_read_line(line, &linelen, fp, &lineno, MUTT_RL_CONT)) != NULL)
199 {
200 const char *const c_config_charset =
201 cs_subset_string(NeoMutt->sub, "config_charset");
202 const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
203 const bool conv = c_config_charset && c_charset;
204 if (conv)
205 {
206 currentline = mutt_str_dup(line);
207 if (!currentline)
208 continue;
209 mutt_ch_convert_string(¤tline, c_config_charset, c_charset, MUTT_ICONV_NO_FLAGS);
210 }
211 else
212 currentline = line;
213
214 mutt_buffer_strcpy(linebuf, currentline);
215
216 mutt_buffer_reset(err);
217 line_rc = mutt_parse_rc_buffer(linebuf, token, err);
218 if (line_rc == MUTT_CMD_ERROR)
219 {
220 mutt_error(_("Error in %s, line %d: %s"), rcfile, lineno, err->data);
221 if (--rc < -MAX_ERRS)
222 {
223 if (conv)
224 FREE(¤tline);
225 break;
226 }
227 }
228 else if (line_rc == MUTT_CMD_WARNING)
229 {
230 /* Warning */
231 mutt_warning(_("Warning in %s, line %d: %s"), rcfile, lineno, err->data);
232 warnings++;
233 }
234 else if (line_rc == MUTT_CMD_FINISH)
235 {
236 break; /* Found "finish" command */
237 }
238 else
239 {
240 if (rc < 0)
241 rc = -1;
242 }
243 if (conv)
244 FREE(¤tline);
245 }
246
247 FREE(&line);
248 mutt_file_fclose(&fp);
249 if (pid != -1)
250 filter_wait(pid);
251
252 if (rc)
253 {
254 /* the neomuttrc source keyword */
255 mutt_buffer_reset(err);
256 mutt_buffer_printf(err, (rc >= -MAX_ERRS) ? _("source: errors in %s") : _("source: reading aborted due to too many errors in %s"),
257 rcfile);
258 rc = -1;
259 }
260 else
261 {
262 /* Don't alias errors with warnings */
263 if (warnings > 0)
264 {
265 mutt_buffer_printf(err, ngettext("source: %d warning in %s", "source: %d warnings in %s", warnings),
266 warnings, rcfile);
267 rc = -2;
268 }
269 }
270
271 if (!ispipe && !STAILQ_EMPTY(&MuttrcStack))
272 {
273 struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
274 STAILQ_REMOVE_HEAD(&MuttrcStack, entries);
275 FREE(&np->data);
276 FREE(&np);
277 }
278
279 mutt_buffer_pool_release(&token);
280 mutt_buffer_pool_release(&linebuf);
281 return rc;
282 }
283
284 /**
285 * parse_cd - Parse the 'cd' command - Implements Command::parse() - @ingroup command_parse
286 */
parse_cd(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)287 enum CommandResult parse_cd(struct Buffer *buf, struct Buffer *s, intptr_t data,
288 struct Buffer *err)
289 {
290 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
291 mutt_buffer_expand_path(buf);
292 if (mutt_buffer_len(buf) == 0)
293 {
294 if (HomeDir)
295 mutt_buffer_strcpy(buf, HomeDir);
296 else
297 {
298 mutt_buffer_printf(err, _("%s: too few arguments"), "cd");
299 return MUTT_CMD_ERROR;
300 }
301 }
302
303 if (chdir(mutt_buffer_string(buf)) != 0)
304 {
305 mutt_buffer_printf(err, "cd: %s", strerror(errno));
306 return MUTT_CMD_ERROR;
307 }
308
309 return MUTT_CMD_SUCCESS;
310 }
311
312 /**
313 * parse_echo - Parse the 'echo' command - Implements Command::parse() - @ingroup command_parse
314 */
parse_echo(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)315 enum CommandResult parse_echo(struct Buffer *buf, struct Buffer *s,
316 intptr_t data, struct Buffer *err)
317 {
318 if (!MoreArgs(s))
319 {
320 mutt_buffer_printf(err, _("%s: too few arguments"), "echo");
321 return MUTT_CMD_WARNING;
322 }
323 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
324 OptForceRefresh = true;
325 mutt_message("%s", buf->data);
326 OptForceRefresh = false;
327 mutt_sleep(0);
328
329 return MUTT_CMD_SUCCESS;
330 }
331
332 /**
333 * parse_finish - Parse the 'finish' command - Implements Command::parse() - @ingroup command_parse
334 * @retval #MUTT_CMD_FINISH Stop processing the current file
335 * @retval #MUTT_CMD_WARNING Failed
336 *
337 * If the 'finish' command is found, we should stop reading the current file.
338 */
parse_finish(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)339 enum CommandResult parse_finish(struct Buffer *buf, struct Buffer *s,
340 intptr_t data, struct Buffer *err)
341 {
342 if (MoreArgs(s))
343 {
344 mutt_buffer_printf(err, _("%s: too many arguments"), "finish");
345 return MUTT_CMD_WARNING;
346 }
347
348 return MUTT_CMD_FINISH;
349 }
350
351 /**
352 * parse_group - Parse the 'group' and 'ungroup' commands - Implements Command::parse() - @ingroup command_parse
353 */
parse_group(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)354 enum CommandResult parse_group(struct Buffer *buf, struct Buffer *s,
355 intptr_t data, struct Buffer *err)
356 {
357 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
358 enum GroupState state = GS_NONE;
359
360 do
361 {
362 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
363 if (parse_grouplist(&gl, buf, s, err) == -1)
364 goto bail;
365
366 if ((data == MUTT_UNGROUP) && mutt_istr_equal(buf->data, "*"))
367 {
368 mutt_grouplist_clear(&gl);
369 goto out;
370 }
371
372 if (mutt_istr_equal(buf->data, "-rx"))
373 state = GS_RX;
374 else if (mutt_istr_equal(buf->data, "-addr"))
375 state = GS_ADDR;
376 else
377 {
378 switch (state)
379 {
380 case GS_NONE:
381 mutt_buffer_printf(err, _("%sgroup: missing -rx or -addr"),
382 (data == MUTT_UNGROUP) ? "un" : "");
383 goto warn;
384
385 case GS_RX:
386 if ((data == MUTT_GROUP) &&
387 (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0))
388 {
389 goto bail;
390 }
391 else if ((data == MUTT_UNGROUP) &&
392 (mutt_grouplist_remove_regex(&gl, buf->data) < 0))
393 {
394 goto bail;
395 }
396 break;
397
398 case GS_ADDR:
399 {
400 char *estr = NULL;
401 struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
402 mutt_addrlist_parse2(&al, buf->data);
403 if (TAILQ_EMPTY(&al))
404 goto bail;
405 if (mutt_addrlist_to_intl(&al, &estr))
406 {
407 mutt_buffer_printf(err, _("%sgroup: warning: bad IDN '%s'"),
408 (data == 1) ? "un" : "", estr);
409 mutt_addrlist_clear(&al);
410 FREE(&estr);
411 goto bail;
412 }
413 if (data == MUTT_GROUP)
414 mutt_grouplist_add_addrlist(&gl, &al);
415 else if (data == MUTT_UNGROUP)
416 mutt_grouplist_remove_addrlist(&gl, &al);
417 mutt_addrlist_clear(&al);
418 break;
419 }
420 }
421 }
422 } while (MoreArgs(s));
423
424 out:
425 mutt_grouplist_destroy(&gl);
426 return MUTT_CMD_SUCCESS;
427
428 bail:
429 mutt_grouplist_destroy(&gl);
430 return MUTT_CMD_ERROR;
431
432 warn:
433 mutt_grouplist_destroy(&gl);
434 return MUTT_CMD_WARNING;
435 }
436
437 /**
438 * parse_ifdef - Parse the 'ifdef' and 'ifndef' commands - Implements Command::parse() - @ingroup command_parse
439 *
440 * The 'ifdef' command allows conditional elements in the config file.
441 * If a given variable, function, command or compile-time symbol exists, then
442 * read the rest of the line of config commands.
443 * e.g.
444 * ifdef sidebar source ~/.neomutt/sidebar.rc
445 *
446 * If (data == 1) then it means use the 'ifndef' (if-not-defined) command.
447 * e.g.
448 * ifndef imap finish
449 */
parse_ifdef(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)450 enum CommandResult parse_ifdef(struct Buffer *buf, struct Buffer *s,
451 intptr_t data, struct Buffer *err)
452 {
453 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
454
455 // is the item defined as:
456 bool res = cs_subset_lookup(NeoMutt->sub, buf->data) // a variable?
457 || feature_enabled(buf->data) // a compiled-in feature?
458 || is_function(buf->data) // a function?
459 || mutt_command_get(buf->data) // a command?
460 || myvar_get(buf->data) // a my_ variable?
461 || mutt_str_getenv(buf->data); // an environment variable?
462
463 if (!MoreArgs(s))
464 {
465 mutt_buffer_printf(err, _("%s: too few arguments"), (data ? "ifndef" : "ifdef"));
466 return MUTT_CMD_WARNING;
467 }
468 mutt_extract_token(buf, s, MUTT_TOKEN_SPACE);
469
470 /* ifdef KNOWN_SYMBOL or ifndef UNKNOWN_SYMBOL */
471 if ((res && (data == 0)) || (!res && (data == 1)))
472 {
473 enum CommandResult rc = mutt_parse_rc_line(buf->data, err);
474 if (rc == MUTT_CMD_ERROR)
475 {
476 mutt_error(_("Error: %s"), err->data);
477 return MUTT_CMD_ERROR;
478 }
479 return rc;
480 }
481 return MUTT_CMD_SUCCESS;
482 }
483
484 /**
485 * parse_ignore - Parse the 'ignore' command - Implements Command::parse() - @ingroup command_parse
486 */
parse_ignore(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)487 enum CommandResult parse_ignore(struct Buffer *buf, struct Buffer *s,
488 intptr_t data, struct Buffer *err)
489 {
490 do
491 {
492 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
493 remove_from_stailq(&UnIgnore, buf->data);
494 add_to_stailq(&Ignore, buf->data);
495 } while (MoreArgs(s));
496
497 return MUTT_CMD_SUCCESS;
498 }
499
500 /**
501 * parse_lists - Parse the 'lists' command - Implements Command::parse() - @ingroup command_parse
502 */
parse_lists(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)503 enum CommandResult parse_lists(struct Buffer *buf, struct Buffer *s,
504 intptr_t data, struct Buffer *err)
505 {
506 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
507
508 do
509 {
510 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
511
512 if (parse_grouplist(&gl, buf, s, err) == -1)
513 goto bail;
514
515 mutt_regexlist_remove(&UnMailLists, buf->data);
516
517 if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
518 goto bail;
519
520 if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
521 goto bail;
522 } while (MoreArgs(s));
523
524 mutt_grouplist_destroy(&gl);
525 return MUTT_CMD_SUCCESS;
526
527 bail:
528 mutt_grouplist_destroy(&gl);
529 return MUTT_CMD_ERROR;
530 }
531
532 /**
533 * parse_mailboxes - Parse the 'mailboxes' command - Implements Command::parse() - @ingroup command_parse
534 *
535 * This is also used by 'virtual-mailboxes'.
536 */
parse_mailboxes(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)537 enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s,
538 intptr_t data, struct Buffer *err)
539 {
540 while (MoreArgs(s))
541 {
542 struct Mailbox *m = mailbox_new();
543
544 if (data & MUTT_NAMED)
545 {
546 // This may be empty, e.g. `named-mailboxes "" +inbox`
547 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
548 m->name = mutt_buffer_strdup(buf);
549 }
550
551 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
552 if (mutt_buffer_is_empty(buf))
553 {
554 /* Skip empty tokens. */
555 mailbox_free(&m);
556 continue;
557 }
558
559 mutt_buffer_strcpy(&m->pathbuf, buf->data);
560 const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
561 /* int rc = */ mx_path_canon2(m, c_folder);
562
563 if (m->type <= MUTT_UNKNOWN)
564 {
565 mutt_error("Unknown Mailbox: %s", m->realpath);
566 mailbox_free(&m);
567 return MUTT_CMD_ERROR;
568 }
569
570 bool new_account = false;
571 struct Account *a = mx_ac_find(m);
572 if (!a)
573 {
574 a = account_new(NULL, NeoMutt->sub);
575 a->type = m->type;
576 new_account = true;
577 }
578
579 if (!new_account)
580 {
581 struct Mailbox *m_old = mx_mbox_find(a, m->realpath);
582 if (m_old)
583 {
584 const bool show = (m_old->flags == MB_HIDDEN);
585 if (show)
586 {
587 m_old->flags = MB_NORMAL;
588 m_old->gen = mailbox_gen();
589 }
590
591 const bool rename = (data & MUTT_NAMED) && !mutt_str_equal(m_old->name, m->name);
592 if (rename)
593 {
594 mutt_str_replace(&m_old->name, m->name);
595 }
596
597 mailbox_free(&m);
598 continue;
599 }
600 }
601
602 if (!mx_ac_add(a, m))
603 {
604 //error
605 mailbox_free(&m);
606 if (new_account)
607 {
608 cs_subset_free(&a->sub);
609 FREE(&a->name);
610 notify_free(&a->notify);
611 FREE(&a);
612 }
613 continue;
614 }
615 if (new_account)
616 {
617 neomutt_account_add(NeoMutt, a);
618 }
619
620 #ifdef USE_INOTIFY
621 mutt_monitor_add(m);
622 #endif
623 }
624 return MUTT_CMD_SUCCESS;
625 }
626
627 /**
628 * parse_my_hdr - Parse the 'my_hdr' command - Implements Command::parse() - @ingroup command_parse
629 */
parse_my_hdr(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)630 enum CommandResult parse_my_hdr(struct Buffer *buf, struct Buffer *s,
631 intptr_t data, struct Buffer *err)
632 {
633 mutt_extract_token(buf, s, MUTT_TOKEN_SPACE | MUTT_TOKEN_QUOTE);
634 char *p = strpbrk(buf->data, ": \t");
635 if (!p || (*p != ':'))
636 {
637 mutt_buffer_strcpy(err, _("invalid header field"));
638 return MUTT_CMD_WARNING;
639 }
640
641 struct EventHeader ev_h = { buf->data };
642 struct ListNode *n = header_find(&UserHeader, buf->data);
643
644 if (n)
645 {
646 header_update(n, buf->data);
647 mutt_debug(LL_NOTIFY, "NT_HEADER_CHANGE: %s\n", buf->data);
648 notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_CHANGE, &ev_h);
649 }
650 else
651 {
652 header_add(&UserHeader, buf->data);
653 mutt_debug(LL_NOTIFY, "NT_HEADER_ADD: %s\n", buf->data);
654 notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_ADD, &ev_h);
655 }
656
657 return MUTT_CMD_SUCCESS;
658 }
659
660 /**
661 * parse_set - Parse the 'set' family of commands - Implements Command::parse() - @ingroup command_parse
662 *
663 * This is used by 'reset', 'set', 'toggle' and 'unset'.
664 */
parse_set(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)665 enum CommandResult parse_set(struct Buffer *buf, struct Buffer *s,
666 intptr_t data, struct Buffer *err)
667 {
668 /* The order must match `enum MuttSetCommand` */
669 static const char *set_commands[] = { "set", "toggle", "unset", "reset" };
670
671 int rc = 0;
672
673 while (MoreArgs(s))
674 {
675 bool prefix = false;
676 bool query = false;
677 bool inv = (data == MUTT_SET_INV);
678 bool reset = (data == MUTT_SET_RESET);
679 bool unset = (data == MUTT_SET_UNSET);
680
681 if (*s->dptr == '?')
682 {
683 prefix = true;
684 query = true;
685 s->dptr++;
686 }
687 else if (mutt_str_startswith(s->dptr, "no"))
688 {
689 prefix = true;
690 unset = !unset;
691 s->dptr += 2;
692 }
693 else if (mutt_str_startswith(s->dptr, "inv"))
694 {
695 prefix = true;
696 inv = !inv;
697 s->dptr += 3;
698 }
699 else if (*s->dptr == '&')
700 {
701 prefix = true;
702 reset = true;
703 s->dptr++;
704 }
705
706 if (prefix && (data != MUTT_SET_SET))
707 {
708 mutt_buffer_printf(err, _("Can't use 'inv', 'no', '&' or '?' with the '%s' command"),
709 set_commands[data]);
710 return MUTT_CMD_WARNING;
711 }
712
713 /* get the variable name */
714 mutt_extract_token(buf, s, MUTT_TOKEN_EQUAL | MUTT_TOKEN_QUESTION | MUTT_TOKEN_PLUS | MUTT_TOKEN_MINUS);
715
716 bool bq = false;
717 bool equals = false;
718 bool increment = false;
719 bool decrement = false;
720
721 struct HashElem *he = NULL;
722 bool my = mutt_str_startswith(buf->data, "my_");
723 if (!my)
724 {
725 he = cs_subset_lookup(NeoMutt->sub, buf->data);
726 if (!he)
727 {
728 if (reset && mutt_str_equal(buf->data, "all"))
729 {
730 struct HashElem **list = get_elem_list(NeoMutt->sub->cs);
731 if (!list)
732 return MUTT_CMD_ERROR;
733
734 for (size_t i = 0; list[i]; i++)
735 cs_subset_he_reset(NeoMutt->sub, list[i], NULL);
736
737 FREE(&list);
738 break;
739 }
740 else
741 {
742 mutt_buffer_printf(err, _("%s: unknown variable"), buf->data);
743 return MUTT_CMD_ERROR;
744 }
745 }
746
747 // Use the correct name if a synonym is used
748 mutt_buffer_strcpy(buf, he->key.strkey);
749
750 bq = ((DTYPE(he->type) == DT_BOOL) || (DTYPE(he->type) == DT_QUAD));
751 }
752
753 if (*s->dptr == '?')
754 {
755 if (prefix)
756 {
757 mutt_buffer_printf(err,
758 _("Can't use a prefix when querying a variable"));
759 return MUTT_CMD_WARNING;
760 }
761
762 if (reset || unset || inv)
763 {
764 mutt_buffer_printf(err, _("Can't query a variable with the '%s' command"),
765 set_commands[data]);
766 return MUTT_CMD_WARNING;
767 }
768
769 query = true;
770 s->dptr++;
771 }
772 else if (*s->dptr == '+' || *s->dptr == '-')
773 {
774 if (prefix)
775 {
776 mutt_buffer_printf(
777 err,
778 _("Can't use prefix when incrementing or decrementing a variable"));
779 return MUTT_CMD_WARNING;
780 }
781
782 if (reset || unset || inv)
783 {
784 mutt_buffer_printf(err, _("Can't set a variable with the '%s' command"),
785 set_commands[data]);
786 return MUTT_CMD_WARNING;
787 }
788 if (*s->dptr == '+')
789 increment = true;
790 else
791 decrement = true;
792
793 if (my && decrement)
794 {
795 mutt_buffer_printf(err, _("Can't decrement a my_ variable"), set_commands[data]);
796 return MUTT_CMD_WARNING;
797 }
798 s->dptr++;
799 if (*s->dptr == '=')
800 {
801 equals = true;
802 s->dptr++;
803 }
804 }
805 else if (*s->dptr == '=')
806 {
807 if (prefix)
808 {
809 mutt_buffer_printf(err, _("Can't use prefix when setting a variable"));
810 return MUTT_CMD_WARNING;
811 }
812
813 if (reset || unset || inv)
814 {
815 mutt_buffer_printf(err, _("Can't set a variable with the '%s' command"),
816 set_commands[data]);
817 return MUTT_CMD_WARNING;
818 }
819
820 equals = true;
821 s->dptr++;
822 }
823
824 if (!bq && (inv || (unset && prefix)))
825 {
826 if (data == MUTT_SET_SET)
827 {
828 mutt_buffer_printf(err, _("Prefixes 'no' and 'inv' may only be used "
829 "with bool/quad variables"));
830 }
831 else
832 {
833 mutt_buffer_printf(err, _("Command '%s' can only be used with bool/quad variables"),
834 set_commands[data]);
835 }
836 return MUTT_CMD_WARNING;
837 }
838
839 if (reset)
840 {
841 // mutt_buffer_printf(err, "ACT24 reset variable %s", buf->data);
842 if (he)
843 {
844 rc = cs_subset_he_reset(NeoMutt->sub, he, err);
845 if (CSR_RESULT(rc) != CSR_SUCCESS)
846 return MUTT_CMD_ERROR;
847 }
848 else
849 {
850 myvar_del(buf->data);
851 }
852 continue;
853 }
854
855 if ((data == MUTT_SET_SET) && !inv && !unset)
856 {
857 if (query)
858 {
859 // mutt_buffer_printf(err, "ACT08 query variable %s", buf->data);
860 if (he)
861 {
862 mutt_buffer_addstr(err, buf->data);
863 mutt_buffer_addch(err, '=');
864 mutt_buffer_reset(buf);
865 rc = cs_subset_he_string_get(NeoMutt->sub, he, buf);
866 if (CSR_RESULT(rc) != CSR_SUCCESS)
867 {
868 mutt_buffer_addstr(err, buf->data);
869 return MUTT_CMD_ERROR;
870 }
871 if (DTYPE(he->type) == DT_PATH)
872 mutt_pretty_mailbox(buf->data, buf->dsize);
873 pretty_var(buf->data, err);
874 }
875 else
876 {
877 const char *val = myvar_get(buf->data);
878 if (val)
879 {
880 mutt_buffer_addstr(err, buf->data);
881 mutt_buffer_addch(err, '=');
882 pretty_var(val, err);
883 }
884 else
885 {
886 mutt_buffer_printf(err, _("%s: unknown variable"), buf->data);
887 return MUTT_CMD_ERROR;
888 }
889 }
890 break;
891 }
892 else if (equals)
893 {
894 // mutt_buffer_printf(err, "ACT11 set variable %s to ", buf->data);
895 const char *name = NULL;
896 if (my)
897 {
898 name = mutt_str_dup(buf->data);
899 }
900 mutt_extract_token(buf, s, MUTT_TOKEN_BACKTICK_VARS);
901 if (my)
902 {
903 assert(!decrement);
904 if (increment)
905 {
906 myvar_append(name, buf->data);
907 }
908 else
909 {
910 myvar_set(name, buf->data);
911 }
912 FREE(&name);
913 }
914 else
915 {
916 if (DTYPE(he->type) == DT_PATH)
917 {
918 if (he->type & (DT_PATH_DIR | DT_PATH_FILE))
919 mutt_buffer_expand_path(buf);
920 else
921 mutt_path_tilde(buf->data, buf->dsize, HomeDir);
922 }
923 else if (IS_MAILBOX(he))
924 {
925 mutt_buffer_expand_path(buf);
926 }
927 else if (IS_COMMAND(he))
928 {
929 struct Buffer scratch = mutt_buffer_make(1024);
930 mutt_buffer_copy(&scratch, buf);
931
932 if (!mutt_str_equal(buf->data, "builtin"))
933 {
934 mutt_buffer_expand_path(&scratch);
935 }
936 mutt_buffer_reset(buf);
937 mutt_buffer_addstr(buf, mutt_buffer_string(&scratch));
938 mutt_buffer_dealloc(&scratch);
939 }
940 if (increment)
941 {
942 rc = cs_subset_he_string_plus_equals(NeoMutt->sub, he, buf->data, err);
943 }
944 else if (decrement)
945 {
946 rc = cs_subset_he_string_minus_equals(NeoMutt->sub, he, buf->data, err);
947 }
948 else
949 {
950 rc = cs_subset_he_string_set(NeoMutt->sub, he, buf->data, err);
951 }
952 if (CSR_RESULT(rc) != CSR_SUCCESS)
953 return MUTT_CMD_ERROR;
954 }
955 continue;
956 }
957 else
958 {
959 if (bq)
960 {
961 // mutt_buffer_printf(err, "ACT23 set variable %s to 'yes'", buf->data);
962 rc = cs_subset_he_native_set(NeoMutt->sub, he, true, err);
963 if (CSR_RESULT(rc) != CSR_SUCCESS)
964 return MUTT_CMD_ERROR;
965 continue;
966 }
967 else
968 {
969 // mutt_buffer_printf(err, "ACT10 query variable %s", buf->data);
970 if (he)
971 {
972 mutt_buffer_addstr(err, buf->data);
973 mutt_buffer_addch(err, '=');
974 mutt_buffer_reset(buf);
975 rc = cs_subset_he_string_get(NeoMutt->sub, he, buf);
976 if (CSR_RESULT(rc) != CSR_SUCCESS)
977 {
978 mutt_buffer_addstr(err, buf->data);
979 return MUTT_CMD_ERROR;
980 }
981 if (DTYPE(he->type) == DT_PATH)
982 mutt_pretty_mailbox(buf->data, buf->dsize);
983 pretty_var(buf->data, err);
984 }
985 else
986 {
987 const char *val = myvar_get(buf->data);
988 if (val)
989 {
990 mutt_buffer_addstr(err, buf->data);
991 mutt_buffer_addch(err, '=');
992 pretty_var(val, err);
993 }
994 else
995 {
996 mutt_buffer_printf(err, _("%s: unknown variable"), buf->data);
997 return MUTT_CMD_ERROR;
998 }
999 }
1000 break;
1001 }
1002 }
1003 }
1004
1005 if (my)
1006 {
1007 myvar_del(buf->data);
1008 }
1009 else if (bq)
1010 {
1011 if (inv)
1012 {
1013 // mutt_buffer_printf(err, "ACT25 TOGGLE bool/quad variable %s", buf->data);
1014 if (DTYPE(he->type) == DT_BOOL)
1015 bool_he_toggle(NeoMutt->sub, he, err);
1016 else
1017 quad_he_toggle(NeoMutt->sub, he, err);
1018 }
1019 else
1020 {
1021 // mutt_buffer_printf(err, "ACT26 UNSET bool/quad variable %s", buf->data);
1022 rc = cs_subset_he_native_set(NeoMutt->sub, he, false, err);
1023 if (CSR_RESULT(rc) != CSR_SUCCESS)
1024 return MUTT_CMD_ERROR;
1025 }
1026 continue;
1027 }
1028 else
1029 {
1030 rc = cs_subset_he_string_set(NeoMutt->sub, he, NULL, err);
1031 if (CSR_RESULT(rc) != CSR_SUCCESS)
1032 return MUTT_CMD_ERROR;
1033 }
1034 }
1035
1036 return MUTT_CMD_SUCCESS;
1037 }
1038
1039 /**
1040 * parse_setenv - Parse the 'setenv' and 'unsetenv' commands - Implements Command::parse() - @ingroup command_parse
1041 */
parse_setenv(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1042 enum CommandResult parse_setenv(struct Buffer *buf, struct Buffer *s,
1043 intptr_t data, struct Buffer *err)
1044 {
1045 char **envp = mutt_envlist_getlist();
1046
1047 bool query = false;
1048 bool prefix = false;
1049 bool unset = (data == MUTT_SET_UNSET);
1050
1051 if (!MoreArgs(s))
1052 {
1053 mutt_buffer_printf(err, _("%s: too few arguments"), "setenv");
1054 return MUTT_CMD_WARNING;
1055 }
1056
1057 if (*s->dptr == '?')
1058 {
1059 query = true;
1060 prefix = true;
1061
1062 if (unset)
1063 {
1064 mutt_buffer_printf(err, _("Can't query a variable with the '%s' command"), "unsetenv");
1065 return MUTT_CMD_WARNING;
1066 }
1067
1068 s->dptr++;
1069 }
1070
1071 /* get variable name */
1072 mutt_extract_token(buf, s, MUTT_TOKEN_EQUAL | MUTT_TOKEN_QUESTION);
1073
1074 if (*s->dptr == '?')
1075 {
1076 if (unset)
1077 {
1078 mutt_buffer_printf(err, _("Can't query a variable with the '%s' command"), "unsetenv");
1079 return MUTT_CMD_WARNING;
1080 }
1081
1082 if (prefix)
1083 {
1084 mutt_buffer_printf(err, _("Can't use a prefix when querying a variable"));
1085 return MUTT_CMD_WARNING;
1086 }
1087
1088 query = true;
1089 s->dptr++;
1090 }
1091
1092 if (query)
1093 {
1094 bool found = false;
1095 while (envp && *envp)
1096 {
1097 /* This will display all matches for "^QUERY" */
1098 if (mutt_str_startswith(*envp, buf->data))
1099 {
1100 if (!found)
1101 {
1102 mutt_endwin();
1103 found = true;
1104 }
1105 puts(*envp);
1106 }
1107 envp++;
1108 }
1109
1110 if (found)
1111 {
1112 mutt_any_key_to_continue(NULL);
1113 return MUTT_CMD_SUCCESS;
1114 }
1115
1116 mutt_buffer_printf(err, _("%s is unset"), buf->data);
1117 return MUTT_CMD_WARNING;
1118 }
1119
1120 if (unset)
1121 {
1122 if (!mutt_envlist_unset(buf->data))
1123 {
1124 mutt_buffer_printf(err, _("%s is unset"), buf->data);
1125 return MUTT_CMD_WARNING;
1126 }
1127 return MUTT_CMD_SUCCESS;
1128 }
1129
1130 /* set variable */
1131
1132 if (*s->dptr == '=')
1133 {
1134 s->dptr++;
1135 SKIPWS(s->dptr);
1136 }
1137
1138 if (!MoreArgs(s))
1139 {
1140 mutt_buffer_printf(err, _("%s: too few arguments"), "setenv");
1141 return MUTT_CMD_WARNING;
1142 }
1143
1144 char *name = mutt_str_dup(buf->data);
1145 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1146 mutt_envlist_set(name, buf->data, true);
1147 FREE(&name);
1148
1149 return MUTT_CMD_SUCCESS;
1150 }
1151
1152 /**
1153 * parse_source - Parse the 'source' command - Implements Command::parse() - @ingroup command_parse
1154 */
parse_source(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1155 enum CommandResult parse_source(struct Buffer *buf, struct Buffer *s,
1156 intptr_t data, struct Buffer *err)
1157 {
1158 char path[PATH_MAX];
1159
1160 do
1161 {
1162 if (mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS) != 0)
1163 {
1164 mutt_buffer_printf(err, _("source: error at %s"), s->dptr);
1165 return MUTT_CMD_ERROR;
1166 }
1167 mutt_str_copy(path, buf->data, sizeof(path));
1168 mutt_expand_path(path, sizeof(path));
1169
1170 if (source_rc(path, err) < 0)
1171 {
1172 mutt_buffer_printf(err, _("source: file %s could not be sourced"), path);
1173 return MUTT_CMD_ERROR;
1174 }
1175
1176 } while (MoreArgs(s));
1177
1178 return MUTT_CMD_SUCCESS;
1179 }
1180
1181 /**
1182 * parse_spam_list - Parse the 'spam' and 'nospam' commands - Implements Command::parse() - @ingroup command_parse
1183 */
parse_spam_list(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1184 enum CommandResult parse_spam_list(struct Buffer *buf, struct Buffer *s,
1185 intptr_t data, struct Buffer *err)
1186 {
1187 struct Buffer templ;
1188
1189 mutt_buffer_init(&templ);
1190
1191 /* Insist on at least one parameter */
1192 if (!MoreArgs(s))
1193 {
1194 if (data == MUTT_SPAM)
1195 mutt_buffer_strcpy(err, _("spam: no matching pattern"));
1196 else
1197 mutt_buffer_strcpy(err, _("nospam: no matching pattern"));
1198 return MUTT_CMD_ERROR;
1199 }
1200
1201 /* Extract the first token, a regex */
1202 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1203
1204 /* data should be either MUTT_SPAM or MUTT_NOSPAM. MUTT_SPAM is for spam commands. */
1205 if (data == MUTT_SPAM)
1206 {
1207 /* If there's a second parameter, it's a template for the spam tag. */
1208 if (MoreArgs(s))
1209 {
1210 mutt_extract_token(&templ, s, MUTT_TOKEN_NO_FLAGS);
1211
1212 /* Add to the spam list. */
1213 if (mutt_replacelist_add(&SpamList, buf->data, templ.data, err) != 0)
1214 {
1215 FREE(&templ.data);
1216 return MUTT_CMD_ERROR;
1217 }
1218 FREE(&templ.data);
1219 }
1220 /* If not, try to remove from the nospam list. */
1221 else
1222 {
1223 mutt_regexlist_remove(&NoSpamList, buf->data);
1224 }
1225
1226 return MUTT_CMD_SUCCESS;
1227 }
1228 /* MUTT_NOSPAM is for nospam commands. */
1229 else if (data == MUTT_NOSPAM)
1230 {
1231 /* nospam only ever has one parameter. */
1232
1233 /* "*" is a special case. */
1234 if (mutt_str_equal(buf->data, "*"))
1235 {
1236 mutt_replacelist_free(&SpamList);
1237 mutt_regexlist_free(&NoSpamList);
1238 return MUTT_CMD_SUCCESS;
1239 }
1240
1241 /* If it's on the spam list, just remove it. */
1242 if (mutt_replacelist_remove(&SpamList, buf->data) != 0)
1243 return MUTT_CMD_SUCCESS;
1244
1245 /* Otherwise, add it to the nospam list. */
1246 if (mutt_regexlist_add(&NoSpamList, buf->data, REG_ICASE, err) != 0)
1247 return MUTT_CMD_ERROR;
1248
1249 return MUTT_CMD_SUCCESS;
1250 }
1251
1252 /* This should not happen. */
1253 mutt_buffer_strcpy(err, "This is no good at all.");
1254 return MUTT_CMD_ERROR;
1255 }
1256
1257 /**
1258 * parse_stailq - Parse a list command - Implements Command::parse() - @ingroup command_parse
1259 *
1260 * This is used by 'alternative_order', 'auto_view' and several others.
1261 */
parse_stailq(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1262 enum CommandResult parse_stailq(struct Buffer *buf, struct Buffer *s,
1263 intptr_t data, struct Buffer *err)
1264 {
1265 do
1266 {
1267 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1268 add_to_stailq((struct ListHead *) data, buf->data);
1269 } while (MoreArgs(s));
1270
1271 return MUTT_CMD_SUCCESS;
1272 }
1273
1274 /**
1275 * parse_subscribe - Parse the 'subscribe' command - Implements Command::parse() - @ingroup command_parse
1276 */
parse_subscribe(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1277 enum CommandResult parse_subscribe(struct Buffer *buf, struct Buffer *s,
1278 intptr_t data, struct Buffer *err)
1279 {
1280 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
1281
1282 do
1283 {
1284 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1285
1286 if (parse_grouplist(&gl, buf, s, err) == -1)
1287 goto bail;
1288
1289 mutt_regexlist_remove(&UnMailLists, buf->data);
1290 mutt_regexlist_remove(&UnSubscribedLists, buf->data);
1291
1292 if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
1293 goto bail;
1294 if (mutt_regexlist_add(&SubscribedLists, buf->data, REG_ICASE, err) != 0)
1295 goto bail;
1296 if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
1297 goto bail;
1298 } while (MoreArgs(s));
1299
1300 mutt_grouplist_destroy(&gl);
1301 return MUTT_CMD_SUCCESS;
1302
1303 bail:
1304 mutt_grouplist_destroy(&gl);
1305 return MUTT_CMD_ERROR;
1306 }
1307
1308 #ifdef USE_IMAP
1309 /**
1310 * parse_subscribe_to - Parse the 'subscribe-to' command - Implements Command::parse() - @ingroup command_parse
1311 *
1312 * The 'subscribe-to' command allows to subscribe to an IMAP-Mailbox.
1313 * Patterns are not supported.
1314 * Use it as follows: subscribe-to =folder
1315 */
parse_subscribe_to(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1316 enum CommandResult parse_subscribe_to(struct Buffer *buf, struct Buffer *s,
1317 intptr_t data, struct Buffer *err)
1318 {
1319 if (!buf || !s || !err)
1320 return MUTT_CMD_ERROR;
1321
1322 mutt_buffer_reset(err);
1323
1324 if (MoreArgs(s))
1325 {
1326 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1327
1328 if (MoreArgs(s))
1329 {
1330 mutt_buffer_printf(err, _("%s: too many arguments"), "subscribe-to");
1331 return MUTT_CMD_WARNING;
1332 }
1333
1334 if (buf->data && (*buf->data != '\0'))
1335 {
1336 /* Expand and subscribe */
1337 if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), true) == 0)
1338 {
1339 mutt_message(_("Subscribed to %s"), buf->data);
1340 return MUTT_CMD_SUCCESS;
1341 }
1342
1343 mutt_buffer_printf(err, _("Could not subscribe to %s"), buf->data);
1344 return MUTT_CMD_ERROR;
1345 }
1346
1347 mutt_debug(LL_DEBUG1, "Corrupted buffer");
1348 return MUTT_CMD_ERROR;
1349 }
1350
1351 mutt_buffer_addstr(err, _("No folder specified"));
1352 return MUTT_CMD_WARNING;
1353 }
1354 #endif
1355
1356 /**
1357 * parse_tag_formats - Parse the 'tag-formats' command - Implements Command::parse() - @ingroup command_parse
1358 */
parse_tag_formats(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1359 enum CommandResult parse_tag_formats(struct Buffer *buf, struct Buffer *s,
1360 intptr_t data, struct Buffer *err)
1361 {
1362 if (!buf || !s)
1363 return MUTT_CMD_ERROR;
1364
1365 char *tmp = NULL;
1366
1367 while (MoreArgs(s))
1368 {
1369 char *tag = NULL, *format = NULL;
1370
1371 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1372 if (buf->data && (*buf->data != '\0'))
1373 tag = mutt_str_dup(buf->data);
1374 else
1375 continue;
1376
1377 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1378 format = mutt_str_dup(buf->data);
1379
1380 /* avoid duplicates */
1381 tmp = mutt_hash_find(TagFormats, format);
1382 if (tmp)
1383 {
1384 mutt_debug(LL_DEBUG3, "tag format '%s' already registered as '%s'\n", format, tmp);
1385 FREE(&tag);
1386 FREE(&format);
1387 continue;
1388 }
1389
1390 mutt_hash_insert(TagFormats, format, tag);
1391 }
1392 return MUTT_CMD_SUCCESS;
1393 }
1394
1395 /**
1396 * parse_tag_transforms - Parse the 'tag-transforms' command - Implements Command::parse() - @ingroup command_parse
1397 */
parse_tag_transforms(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1398 enum CommandResult parse_tag_transforms(struct Buffer *buf, struct Buffer *s,
1399 intptr_t data, struct Buffer *err)
1400 {
1401 if (!buf || !s)
1402 return MUTT_CMD_ERROR;
1403
1404 char *tmp = NULL;
1405
1406 while (MoreArgs(s))
1407 {
1408 char *tag = NULL, *transform = NULL;
1409
1410 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1411 if (buf->data && (*buf->data != '\0'))
1412 tag = mutt_str_dup(buf->data);
1413 else
1414 continue;
1415
1416 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1417 transform = mutt_str_dup(buf->data);
1418
1419 /* avoid duplicates */
1420 tmp = mutt_hash_find(TagTransforms, tag);
1421 if (tmp)
1422 {
1423 mutt_debug(LL_DEBUG3, "tag transform '%s' already registered as '%s'\n", tag, tmp);
1424 FREE(&tag);
1425 FREE(&transform);
1426 continue;
1427 }
1428
1429 mutt_hash_insert(TagTransforms, tag, transform);
1430 }
1431 return MUTT_CMD_SUCCESS;
1432 }
1433
1434 /**
1435 * parse_unignore - Parse the 'unignore' command - Implements Command::parse() - @ingroup command_parse
1436 */
parse_unignore(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1437 enum CommandResult parse_unignore(struct Buffer *buf, struct Buffer *s,
1438 intptr_t data, struct Buffer *err)
1439 {
1440 do
1441 {
1442 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1443
1444 /* don't add "*" to the unignore list */
1445 if (strcmp(buf->data, "*") != 0)
1446 add_to_stailq(&UnIgnore, buf->data);
1447
1448 remove_from_stailq(&Ignore, buf->data);
1449 } while (MoreArgs(s));
1450
1451 return MUTT_CMD_SUCCESS;
1452 }
1453
1454 /**
1455 * parse_unlists - Parse the 'unlists' command - Implements Command::parse() - @ingroup command_parse
1456 */
parse_unlists(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1457 enum CommandResult parse_unlists(struct Buffer *buf, struct Buffer *s,
1458 intptr_t data, struct Buffer *err)
1459 {
1460 mutt_hash_free(&AutoSubscribeCache);
1461 do
1462 {
1463 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1464 mutt_regexlist_remove(&SubscribedLists, buf->data);
1465 mutt_regexlist_remove(&MailLists, buf->data);
1466
1467 if (!mutt_str_equal(buf->data, "*") &&
1468 (mutt_regexlist_add(&UnMailLists, buf->data, REG_ICASE, err) != 0))
1469 {
1470 return MUTT_CMD_ERROR;
1471 }
1472 } while (MoreArgs(s));
1473
1474 return MUTT_CMD_SUCCESS;
1475 }
1476
1477 /**
1478 * do_unmailboxes - Remove a Mailbox from the Sidebar/notifications
1479 * @param m Mailbox to `unmailboxes`
1480 */
do_unmailboxes(struct Mailbox * m)1481 static void do_unmailboxes(struct Mailbox *m)
1482 {
1483 #ifdef USE_INOTIFY
1484 mutt_monitor_remove(m);
1485 #endif
1486 m->flags = MB_HIDDEN;
1487 m->gen = -1;
1488 if (m->opened)
1489 {
1490 struct EventMailbox ev_m = { NULL };
1491 mutt_debug(LL_NOTIFY, "NT_MAILBOX_SWITCH: NULL\n");
1492 notify_send(NeoMutt->notify, NT_MAILBOX, NT_MAILBOX_SWITCH, &ev_m);
1493 }
1494 else
1495 {
1496 account_mailbox_remove(m->account, m);
1497 mailbox_free(&m);
1498 }
1499 }
1500
1501 /**
1502 * do_unmailboxes_star - Remove all Mailboxes from the Sidebar/notifications
1503 */
do_unmailboxes_star(void)1504 static void do_unmailboxes_star(void)
1505 {
1506 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
1507 neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
1508 struct MailboxNode *np = NULL;
1509 struct MailboxNode *nptmp = NULL;
1510 STAILQ_FOREACH_SAFE(np, &ml, entries, nptmp)
1511 {
1512 do_unmailboxes(np->mailbox);
1513 }
1514 neomutt_mailboxlist_clear(&ml);
1515 }
1516
1517 /**
1518 * parse_unmailboxes - Parse the 'unmailboxes' command - Implements Command::parse() - @ingroup command_parse
1519 *
1520 * This is also used by 'unvirtual-mailboxes'
1521 */
parse_unmailboxes(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1522 enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s,
1523 intptr_t data, struct Buffer *err)
1524 {
1525 while (MoreArgs(s))
1526 {
1527 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1528
1529 if (mutt_str_equal(buf->data, "*"))
1530 {
1531 do_unmailboxes_star();
1532 return MUTT_CMD_SUCCESS;
1533 }
1534
1535 mutt_buffer_expand_path(buf);
1536
1537 struct Account *a = NULL;
1538 TAILQ_FOREACH(a, &NeoMutt->accounts, entries)
1539 {
1540 struct Mailbox *m = mx_mbox_find(a, mutt_buffer_string(buf));
1541 if (m)
1542 {
1543 do_unmailboxes(m);
1544 break;
1545 }
1546 }
1547 }
1548 return MUTT_CMD_SUCCESS;
1549 }
1550
1551 /**
1552 * parse_unmy_hdr - Parse the 'unmy_hdr' command - Implements Command::parse() - @ingroup command_parse
1553 */
parse_unmy_hdr(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1554 enum CommandResult parse_unmy_hdr(struct Buffer *buf, struct Buffer *s,
1555 intptr_t data, struct Buffer *err)
1556 {
1557 struct ListNode *np = NULL, *tmp = NULL;
1558 size_t l;
1559
1560 do
1561 {
1562 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1563 if (mutt_str_equal("*", buf->data))
1564 {
1565 /* Clear all headers, send a notification for each header */
1566 STAILQ_FOREACH(np, &UserHeader, entries)
1567 {
1568 mutt_debug(LL_NOTIFY, "NT_HEADER_DELETE: %s\n", np->data);
1569 struct EventHeader ev_h = { np->data };
1570 notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_DELETE, &ev_h);
1571 }
1572 mutt_list_free(&UserHeader);
1573 continue;
1574 }
1575
1576 l = mutt_str_len(buf->data);
1577 if (buf->data[l - 1] == ':')
1578 l--;
1579
1580 STAILQ_FOREACH_SAFE(np, &UserHeader, entries, tmp)
1581 {
1582 if (mutt_istrn_equal(buf->data, np->data, l) && (np->data[l] == ':'))
1583 {
1584 mutt_debug(LL_NOTIFY, "NT_HEADER_DELETE: %s\n", np->data);
1585 struct EventHeader ev_h = { np->data };
1586 notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_DELETE, &ev_h);
1587
1588 header_free(&UserHeader, np);
1589 }
1590 }
1591 } while (MoreArgs(s));
1592 return MUTT_CMD_SUCCESS;
1593 }
1594
1595 /**
1596 * parse_unstailq - Parse an unlist command - Implements Command::parse() - @ingroup command_parse
1597 *
1598 * This is used by 'unalternative_order', 'unauto_view' and several others.
1599 */
parse_unstailq(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1600 enum CommandResult parse_unstailq(struct Buffer *buf, struct Buffer *s,
1601 intptr_t data, struct Buffer *err)
1602 {
1603 do
1604 {
1605 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1606 /* Check for deletion of entire list */
1607 if (mutt_str_equal(buf->data, "*"))
1608 {
1609 mutt_list_free((struct ListHead *) data);
1610 break;
1611 }
1612 remove_from_stailq((struct ListHead *) data, buf->data);
1613 } while (MoreArgs(s));
1614
1615 return MUTT_CMD_SUCCESS;
1616 }
1617
1618 /**
1619 * parse_unsubscribe - Parse the 'unsubscribe' command - Implements Command::parse() - @ingroup command_parse
1620 */
parse_unsubscribe(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1621 enum CommandResult parse_unsubscribe(struct Buffer *buf, struct Buffer *s,
1622 intptr_t data, struct Buffer *err)
1623 {
1624 mutt_hash_free(&AutoSubscribeCache);
1625 do
1626 {
1627 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1628 mutt_regexlist_remove(&SubscribedLists, buf->data);
1629
1630 if (!mutt_str_equal(buf->data, "*") &&
1631 (mutt_regexlist_add(&UnSubscribedLists, buf->data, REG_ICASE, err) != 0))
1632 {
1633 return MUTT_CMD_ERROR;
1634 }
1635 } while (MoreArgs(s));
1636
1637 return MUTT_CMD_SUCCESS;
1638 }
1639
1640 #ifdef USE_IMAP
1641 /**
1642 * parse_unsubscribe_from - Parse the 'unsubscribe-from' command - Implements Command::parse() - @ingroup command_parse
1643 *
1644 * The 'unsubscribe-from' command allows to unsubscribe from an IMAP-Mailbox.
1645 * Patterns are not supported.
1646 * Use it as follows: unsubscribe-from =folder
1647 */
parse_unsubscribe_from(struct Buffer * buf,struct Buffer * s,intptr_t data,struct Buffer * err)1648 enum CommandResult parse_unsubscribe_from(struct Buffer *buf, struct Buffer *s,
1649 intptr_t data, struct Buffer *err)
1650 {
1651 if (!buf || !s || !err)
1652 return MUTT_CMD_ERROR;
1653
1654 if (MoreArgs(s))
1655 {
1656 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1657
1658 if (MoreArgs(s))
1659 {
1660 mutt_buffer_printf(err, _("%s: too many arguments"), "unsubscribe-from");
1661 return MUTT_CMD_WARNING;
1662 }
1663
1664 if (buf->data && (*buf->data != '\0'))
1665 {
1666 /* Expand and subscribe */
1667 if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), false) == 0)
1668 {
1669 mutt_message(_("Unsubscribed from %s"), buf->data);
1670 return MUTT_CMD_SUCCESS;
1671 }
1672
1673 mutt_buffer_printf(err, _("Could not unsubscribe from %s"), buf->data);
1674 return MUTT_CMD_ERROR;
1675 }
1676
1677 mutt_debug(LL_DEBUG1, "Corrupted buffer");
1678 return MUTT_CMD_ERROR;
1679 }
1680
1681 mutt_buffer_addstr(err, _("No folder specified"));
1682 return MUTT_CMD_WARNING;
1683 }
1684 #endif
1685
1686 /**
1687 * clear_source_stack - Free memory from the stack used for the source command
1688 */
clear_source_stack(void)1689 void clear_source_stack(void)
1690 {
1691 mutt_list_free(&MuttrcStack);
1692 }
1693